diff --git a/ozgcloud-keycloak-operator/pom.xml b/ozgcloud-keycloak-operator/pom.xml index a538436b9505cfc1048d027410c294a60dd7b969..051d55f11f93bef81c375abb0884c533121541d9 100644 --- a/ozgcloud-keycloak-operator/pom.xml +++ b/ozgcloud-keycloak-operator/pom.xml @@ -20,11 +20,6 @@ </properties> <dependencies> - <!-- spring --> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter</artifactId> - </dependency> <dependency> <groupId>io.javaoperatorsdk</groupId> <artifactId>operator-framework-spring-boot-starter</artifactId> diff --git a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteService.java b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteService.java index 5829cacba9f23de8c68a53ecf9667835453df742..f896921b430c970710edc4bb3b8d3c3b9f70a031 100644 --- a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteService.java +++ b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteService.java @@ -29,6 +29,7 @@ import java.util.Optional; import org.keycloak.admin.client.Keycloak; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.userprofile.config.UPConfig; import org.springframework.stereotype.Component; import de.ozgcloud.operator.keycloak.KeycloakException; @@ -69,4 +70,17 @@ class KeycloakRealmRemoteService { keycloak.realm(realm).roles().create(role); } + UPConfig getUserProfileConfig(RealmRepresentation realmRepresentation) { + return keycloak.realm(realmRepresentation.getRealm()) + .users() + .userProfile() + .getConfiguration(); + } + + void updateUserProfileConfig(RealmRepresentation realmRepresentation, UPConfig userProfileConfig) { + keycloak.realm(realmRepresentation.getRealm()) + .users() + .userProfile() + .update(userProfileConfig); + } } diff --git a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmService.java b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmService.java index d57ef9c49f5d55ded30884f5bbfbb095e8e2edc2..eca8fbe736f702eb1a8c5952c3d6c3d88f84f7d4 100644 --- a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmService.java +++ b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmService.java @@ -24,8 +24,12 @@ package de.ozgcloud.operator.keycloak.realm; import java.util.Optional; +import java.util.Set; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.userprofile.config.UPAttribute; +import org.keycloak.representations.userprofile.config.UPAttributePermissions; +import org.keycloak.representations.userprofile.config.UPConfig; import org.springframework.stereotype.Component; import de.ozgcloud.operator.keycloak.KeycloakGenericRemoteService; @@ -37,6 +41,8 @@ import lombok.extern.log4j.Log4j2; @Component class KeycloakRealmService { + public static final String USER_PROFILE_CONFIG_OZGCLOUD_ATTRIBUTE_NAME = "ozgCloudUserId"; + private final KeycloakRealmRemoteService remoteService; private final KeycloakRealmMapper mapper; @@ -55,6 +61,7 @@ class KeycloakRealmService { LOG.debug("{}: Updating existing realm...", existingRealm); var realmRepresentation = mapper.update(existingRealm, spec); remoteService.updateRealm(realmRepresentation); + addUserProfileAttributes(realmRepresentation); } catch (Exception e) { LOG.warn(existingRealm + ": Updating existing realm failed: ", e); throw e; @@ -65,8 +72,13 @@ class KeycloakRealmService { Optional.of(realm) .map(mapper::map) .map(realmRepresentation -> addRealmName(realmRepresentation, realmName)) + // TODO dieser Filter kann vermutlich gelöscht werden, die Prüfung auf + // realmExists passiert bereits vorher .filter(realmRepresentation -> !keycloakGenericRemoteService.realmExists(realmName)) - .ifPresent(remoteService::createRealm); + .ifPresent(realmRepresentation -> { + remoteService.createRealm(realmRepresentation); + addUserProfileAttributes(realmRepresentation); + }); } void addOrUpdateRealmRoles(OzgCloudKeycloakRealmSpec spec, String realm) { @@ -89,4 +101,18 @@ class KeycloakRealmService { public boolean realmExists(String realmName) { return keycloakGenericRemoteService.realmExists(realmName); } + + void addUserProfileAttributes(RealmRepresentation realmRepresentation) { + UPConfig userProfileConfig = remoteService.getUserProfileConfig(realmRepresentation); + + updateUserProfileConfig(userProfileConfig); + + remoteService.updateUserProfileConfig(realmRepresentation, userProfileConfig); + } + + void updateUserProfileConfig(UPConfig userProfileConfig) { + userProfileConfig.addOrReplaceAttribute( + new UPAttribute(USER_PROFILE_CONFIG_OZGCLOUD_ATTRIBUTE_NAME, + new UPAttributePermissions(Set.of("admin", "user"), Set.of("admin", "user")))); + } } diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteServiceTest.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteServiceTest.java index 0b20c33a139e924d42890b07d21cd3a962f99097..83bfaf57d7f03bf1240ee62e685d7a6f131d64f5 100644 --- a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteServiceTest.java +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmRemoteServiceTest.java @@ -23,10 +23,10 @@ */ package de.ozgcloud.operator.keycloak.realm; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; import java.util.List; import java.util.Optional; @@ -39,7 +39,11 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmsResource; import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.RolesResource; +import org.keycloak.admin.client.resource.UserProfileResource; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.userprofile.config.UPConfig; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -108,7 +112,7 @@ class KeycloakRealmRemoteServiceTest { verify(realmResource).update(realm); } } - + @Nested class TestDeleteRealm { @@ -191,4 +195,44 @@ class KeycloakRealmRemoteServiceTest { } } } + + @Nested + class TestUserProfileConfig { + + @Mock + RealmRepresentation realmRepresentation; + + @Mock + UsersResource usersResource; + + @Mock + UserProfileResource userProfileResource; + + @Mock + UPConfig userProfileConfig; + + @BeforeEach + void init() { + when(realmRepresentation.getRealm()).thenReturn(REALM_NAME); + when(keycloak.realm(REALM_NAME)).thenReturn(realmResource); + when(realmResource.users()).thenReturn(usersResource); + when(usersResource.userProfile()).thenReturn(userProfileResource); + } + + @Test + void getUserProfileConfigshouldReturnKeycloakConfiguration() { + when(userProfileResource.getConfiguration()).thenReturn(userProfileConfig); + + var config = remoteService.getUserProfileConfig(realmRepresentation); + + assertThat(config).isSameAs(userProfileConfig); + } + + @Test + void updateUserProfileConfigshouldCallKeycloakUpdate() { + remoteService.updateUserProfileConfig(realmRepresentation, userProfileConfig); + + verify(userProfileResource).update(userProfileConfig); + } + } } diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmServiceTest.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmServiceTest.java index 044c9f8880ef605b0f120514c8f856766d8c3aa6..49184fb5a7f6d74ba0100e9cbf6dbed74e56af87 100644 --- a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmServiceTest.java +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmServiceTest.java @@ -23,6 +23,7 @@ */ package de.ozgcloud.operator.keycloak.realm; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -33,6 +34,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.userprofile.config.UPConfig; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -80,6 +82,7 @@ class KeycloakRealmServiceTest { void shouldCallUpdateRealmIfAlreadyExists() { var existingRealm = RealmRepresentationTestFactory.create(); when(keycloakGenericRemoteService.getRealmRepresentation(REALM_NAME)).thenReturn(Optional.of(existingRealm)); + doNothing().when(service).updateUserProfileConfig(any()); service.createOrUpdateRealm(REALM, REALM_NAME); @@ -92,13 +95,13 @@ class KeycloakRealmServiceTest { verify(keycloakGenericRemoteService).getRealmRepresentation(REALM_NAME); } + @Test void shouldCallAddOrUpdateRealmRoles() { service.createOrUpdateRealm(REALM, REALM_NAME); verify(service).addOrUpdateRealmRoles(REALM, REALM_NAME); } - } @DisplayName("Update Realm") @@ -108,6 +111,7 @@ class KeycloakRealmServiceTest { @BeforeEach void init() { when(mapper.update(any(), any())).thenReturn(realmRepresentation); + when(remoteService.getUserProfileConfig(realmRepresentation)).thenReturn(new UPConfig()); } @Test @@ -125,7 +129,12 @@ class KeycloakRealmServiceTest { verify(mapper).update(realmRepresentation, REALM); } + @Test + void createRealmShouldCallAddAttributes() { + service.updateRealm(realmRepresentation, REALM); + verify(service).addUserProfileAttributes(realmRepresentation); + } } @DisplayName("Add or Update Realm Roles") @@ -170,6 +179,7 @@ class KeycloakRealmServiceTest { @BeforeEach void init() { when(mapper.map(REALM)).thenReturn(realmRepresentation); + when(remoteService.getUserProfileConfig(realmRepresentation)).thenReturn(new UPConfig()); } @Test @@ -197,6 +207,7 @@ class KeycloakRealmServiceTest { @Test void shouldNOTCallCreateRealmIfAlreadyExists() { + reset(remoteService); when(keycloakGenericRemoteService.realmExists(REALM_NAME)).thenReturn(true); service.createRealm(REALM, REALM_NAME); @@ -212,6 +223,13 @@ class KeycloakRealmServiceTest { verify(service).addRealmName(realmRepresentation, REALM_NAME); } + + @Test + void updateRealmShouldCallAddAttributes() { + service.createRealm(REALM, REALM_NAME); + + verify(service).addUserProfileAttributes(realmRepresentation); + } } @Test @@ -232,6 +250,7 @@ class KeycloakRealmServiceTest { verify(remoteService).deleteRealm(REALM_NAME); } } + @Nested class TestRealmExists { @@ -242,5 +261,71 @@ class KeycloakRealmServiceTest { verify(keycloakGenericRemoteService).realmExists(REALM_NAME); } } - + + @Nested + class TestAddUserProfileAttributes { + + private UPConfig userProfileConfig; + + @BeforeEach + void init() { + userProfileConfig = new UPConfig(); + when(remoteService.getUserProfileConfig(realmRepresentation)).thenReturn(userProfileConfig); + } + + @Test + void shouldCallRemoteServiceGetUserProfileConfig() { + service.addUserProfileAttributes(realmRepresentation); + + verify(remoteService).getUserProfileConfig(realmRepresentation); + } + + @Test + void shouldCallRemoteServiceUpdateUserProfileConfig() { + service.addUserProfileAttributes(realmRepresentation); + + verify(remoteService).updateUserProfileConfig(realmRepresentation, userProfileConfig); + } + + @Test + void shouldCallUpdateUserProfileConfig() { + service.addUserProfileAttributes(realmRepresentation); + + verify(service).updateUserProfileConfig(any(UPConfig.class)); + } + } + + @Nested + class TestUpdateUserProfileConfig { + + @Test + void shouldAddUserProfileAttribute() { + var userProfileConfig = new UPConfig(); + + service.updateUserProfileConfig(userProfileConfig); + + assertThat(userProfileConfig.getAttributes()).hasSize(1); + } + + @Test + void shouldAddUserProfileAttributeOzgCloud() { + var userProfileConfig = new UPConfig(); + + service.updateUserProfileConfig(userProfileConfig); + + var attributeName = userProfileConfig.getAttributes().get(0).getName(); + assertThat(attributeName).isEqualTo("ozgCloudUserId"); + } + + @Test + void shouldSetPermissions() { + var userProfileConfig = new UPConfig(); + + service.updateUserProfileConfig(userProfileConfig); + + var permissions = userProfileConfig.getAttributes().get(0).getPermissions(); + assertThat(permissions.getEdit()).contains("admin", "user"); + assertThat(permissions.getView()).contains("admin", "user"); + } + } } diff --git a/pom.xml b/pom.xml index 40f23260f7a3b3bd5e1298d07272dfe64842ae0d..9cac92830811d994aa6f0b0a4bbaa69726370eea 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>3.2.6</version> + <version>3.3.2</version> <relativePath/> </parent> @@ -23,14 +23,13 @@ </modules> <properties> - <spring-boot.version>3.1.8</spring-boot.version> <operator-sdk.version>5.4.1</operator-sdk.version> <!-- tools --> <commons-beanutils.version>1.9.4</commons-beanutils.version> - <lombok.version>1.18.28</lombok.version> +<!-- <lombok.version>1.18.28</lombok.version>--> <mapstruct.version>1.5.5.Final</mapstruct.version> - <keycloak-adapter.version>23.0.6</keycloak-adapter.version> + <keycloak-adapter.version>24.0.5</keycloak-adapter.version> <reflections.version>0.10.2</reflections.version> <validation-api.version>2.0.1.Final</validation-api.version> <lorem.version>2.2</lorem.version> @@ -50,11 +49,6 @@ </properties> <dependencies> - <!-- spring --> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter</artifactId> - </dependency> <dependency> <groupId>io.javaoperatorsdk</groupId> <artifactId>operator-framework-spring-boot-starter</artifactId> @@ -120,7 +114,6 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> - <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>io.javaoperatorsdk</groupId>