From 207d4f7cf372be51af910fb2c6c2094a8fc25a8f Mon Sep 17 00:00:00 2001 From: Felix Reichenbach <felix.reichenbach@mgm-tp.com> Date: Thu, 9 Jan 2025 17:07:39 +0100 Subject: [PATCH] OZG-6733 OZG-7457 provide keycloak api link --- .../admin/common/FeatureToggleProperties.java | 4 +- .../admin/keycloak/KeyCloakRootProcessor.java | 59 +++++++ .../keycloak/KeyCloakRootProcessorITCase.java | 70 ++++++++ .../keycloak/KeyCloakRootProcessorTest.java | 154 ++++++++++++++++++ 4 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessor.java create mode 100644 src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorITCase.java create mode 100644 src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorTest.java diff --git a/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java b/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java index 1589df74..71cdb9ee 100644 --- a/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java +++ b/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java @@ -32,9 +32,11 @@ import lombok.Setter; @Setter @Getter @Configuration -@ConfigurationProperties(prefix = "ozgcloud.feature") +@ConfigurationProperties(prefix = FeatureToggleProperties.FEATURE_TOGGLE_PREFIX) public class FeatureToggleProperties { + public static final String FEATURE_TOGGLE_PREFIX = "ozgcloud.feature"; + private boolean postfach; private boolean benutzerRollen; private boolean organisationsEinheiten; diff --git a/src/main/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessor.java b/src/main/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessor.java new file mode 100644 index 00000000..de6a05b3 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessor.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.admin.keycloak; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelProcessor; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import de.ozgcloud.admin.Root; +import de.ozgcloud.admin.common.FeatureToggleProperties; +import de.ozgcloud.admin.common.user.CurrentUserService; +import de.ozgcloud.admin.common.user.UserRole; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = FeatureToggleProperties.FEATURE_TOGGLE_PREFIX, name = "benutzerRollen", havingValue = "true") +class KeyCloakRootProcessor implements RepresentationModelProcessor<EntityModel<Root>> { + + public static final String REL_USERS = "users"; + + private final KeycloakApiProperties keycloakApiProperties; + private final CurrentUserService currentUserService; + + @Override + public EntityModel<Root> process(EntityModel<Root> model) { + return model.addIf(currentUserService.hasRole(UserRole.ADMIN_ADMIN), () -> Link.of(buildUsersHref(), REL_USERS)); + } + + String buildUsersHref() { + return UriComponentsBuilder.fromUriString(keycloakApiProperties.getUrl()) + .pathSegment("admin", "realms", keycloakApiProperties.getRealm(), "users") // NOSONAR + .build().toUriString(); + } +} diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorITCase.java b/src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorITCase.java new file mode 100644 index 00000000..44976f8c --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorITCase.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.admin.keycloak; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +import de.ozgcloud.common.test.ITCase; + +class KeyCloakRootProcessorITCase { + + @Nested + @SpringBootTest(properties = { + "ozgcloud.feature.benutzerRollen=true" + }) + @ITCase + class TestFeatureEnabled { + + @Autowired + private ApplicationContext applicationContext; + + @Test + void shouldHaveKeyCloakRootProcessorBean() { + assertDoesNotThrow(() -> applicationContext.getBean(KeyCloakRootProcessor.class)); + } + } + + @Nested + @SpringBootTest(properties = { + "ozgcloud.feature.benutzerRollen=false" + }) + @ITCase + class TestFeatureDisabled { + + @Autowired + private ApplicationContext applicationContext; + + @Test + void shouldHaveKeyCloakRootProcessorBean() { + assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(KeyCloakRootProcessor.class)); + } + } +} \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorTest.java b/src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorTest.java new file mode 100644 index 00000000..604e2281 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeyCloakRootProcessorTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.admin.keycloak; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.admin.Root; +import de.ozgcloud.admin.RootTestFactory; +import de.ozgcloud.admin.common.user.CurrentUserService; +import de.ozgcloud.admin.common.user.UserRole; + +class KeyCloakRootProcessorTest { + + @InjectMocks + @Spy + private KeyCloakRootProcessor processor; + @Mock + private CurrentUserService currentUserService; + @Mock + private KeycloakApiProperties keycloakApiProperties; + + @Nested + class TestProcess { + private final String href = LoremIpsum.getInstance().getUrl(); + + @Test + void shouldCheckUserRole() { + processModel(); + + verify(currentUserService).hasRole(UserRole.ADMIN_ADMIN); + } + + @Nested + class TestOnAdminRole { + + @BeforeEach + void givenHasAdminRole() { + when(currentUserService.hasRole(anyString())).thenReturn(true); + doReturn(href).when(processor).buildUsersHref(); + } + + @Test + void shouldCallBuildUsersHref() { + processModel(); + + verify(processor).buildUsersHref(); + } + + @Test + void shouldAddUsersLink() { + var model = processModel(); + + assertThat(model.getLink(KeyCloakRootProcessor.REL_USERS)).isNotEmpty(); + } + + @Test + void shouldSetHref() { + var model = processModel(); + + assertThat(model.getLink(KeyCloakRootProcessor.REL_USERS)) + .get() + .extracting(Link::getHref) + .isEqualTo(href); + } + } + + @Nested + class TestOnWrongUserRole { + + @BeforeEach + void givenHasWrongRole() { + when(currentUserService.hasRole(anyString())).thenReturn(false); + } + + @Test + void shouldNotAddAnyLinks() { + var model = processModel(); + + assertThat(model.getLinks()).isEmpty(); + } + } + + private EntityModel<Root> processModel() { + return processor.process(EntityModel.of(RootTestFactory.create())); + } + } + + @Nested + class TestBuildUsersHref { + private final String baseUrl = "https://keycloak.domain.de"; + private final String realm = LoremIpsum.getInstance().getWords(1); + + @BeforeEach + void mockProperties() { + when(keycloakApiProperties.getUrl()).thenReturn(baseUrl); + when(keycloakApiProperties.getRealm()).thenReturn(realm); + } + + @Test + void shouldGetKeyCloakUrl() { + processor.buildUsersHref(); + + verify(keycloakApiProperties).getUrl(); + } + + @Test + void shouldGetKeyCloakRealm() { + processor.buildUsersHref(); + + verify(keycloakApiProperties).getRealm(); + } + + @Test + void shouldReturnApiEndpoint() { + var uriString = processor.buildUsersHref(); + + assertThat(uriString).isEqualTo(baseUrl + "/admin/realms/" + realm + "/users"); + } + } +} \ No newline at end of file -- GitLab