diff --git a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteService.java b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteService.java index 53e57ffed8c03c1406bc0ecdd4c877b35873aed9..0e003fe54a7c8be9b6f0f61bc1dd367b1c190f4c 100644 --- a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteService.java +++ b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteService.java @@ -34,6 +34,15 @@ public class KeycloakGenericRemoteService { .anyMatch(group -> Objects.equals(groupName, group.getName())); } + public Optional<RealmRepresentation> getRealmRepresentation(String realmName) { + if (realmExists(realmName)) { + return Optional.of(keycloak.realm(realmName).toRepresentation()); + } else { + return Optional.empty(); + } + + } + public Optional<RoleRepresentation> getClientRole(String roleName, String realClientId, String realm) { return Optional.ofNullable(keycloak.realm(realm).clients().get(realClientId)) .orElseThrow(() -> new KeycloakException("Client with ID " + realClientId + " for realm " + realm + " not found.")) @@ -42,4 +51,5 @@ public class KeycloakGenericRemoteService { .stream().filter(role -> Objects.equals(roleName, role.getName())) .findFirst(); } + } diff --git a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapper.java b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapper.java index 7c989b0fc4f1a4f10d916adef6598ea494956327..1c93aa31cc071537b9109ae9d30c752cb32b7c4c 100644 --- a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapper.java +++ b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapper.java @@ -25,39 +25,52 @@ package de.ozgcloud.operator.keycloak.realm; import java.util.HashMap; import java.util.Map; -import java.util.ArrayList; -import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.RolesRepresentation; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.Named; import org.mapstruct.ReportingPolicy; -import de.ozgcloud.operator.keycloak.realm.OzgCloudKeycloakRealmSpec.RealmRole; +import de.ozgcloud.operator.keycloak.realm.OzgCloudKeycloakRealmSpec.KeycloakRealmSMTPServer; @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE) interface KeycloakRealmMapper { - @Mapping(target = "displayName", source = "displayName") - @Mapping(target = "enabled", constant = "true") - @Mapping(target = "resetPasswordAllowed", constant = "true") + public static final String ACTION_TOKEN_GENERATED_BY_USER_LIFE_SPAN = "900"; + public static final String PASSWORD_POLICY = "upperCase(1) and lowerCase(1) and length(8) and notUsername"; + public static final String DEFAULT_LOCALE = "de"; + public static final String RESET_PASSWORD_ALLOWED = "true"; + public static final String ENABLED = "true"; + public static final String INTERNATIONALIZATION_ENABLED = "true"; + + @Mapping(target = "enabled", constant = ENABLED) + @Mapping(target = "resetPasswordAllowed", constant = RESET_PASSWORD_ALLOWED) @Mapping(target = "supportedLocales", source = ".", qualifiedByName = "supportedLocales") - @Mapping(target = "defaultLocale", constant = "de") - @Mapping(target = "internationalizationEnabled", constant = "true") - @Mapping(target = "passwordPolicy", constant = "upperCase(1) and lowerCase(1) and length(8) and notUsername") - @Mapping(target = "actionTokenGeneratedByUserLifespan", constant = "900") + @Mapping(target = "defaultLocale", constant = DEFAULT_LOCALE) + @Mapping(target = "internationalizationEnabled", constant = INTERNATIONALIZATION_ENABLED) + @Mapping(target = "passwordPolicy", constant = PASSWORD_POLICY) + @Mapping(target = "actionTokenGeneratedByUserLifespan", constant = ACTION_TOKEN_GENERATED_BY_USER_LIFE_SPAN) @Mapping(target = "smtpServer", source = "smtpServer", qualifiedByName = "smtpServer") @Mapping(target = "roles.realm", source = "realmRoles") public RealmRepresentation map(OzgCloudKeycloakRealmSpec realm); + @Mapping(target = "enabled", constant = ENABLED) + @Mapping(target = "resetPasswordAllowed", constant = RESET_PASSWORD_ALLOWED) + @Mapping(target = "supportedLocales", source = ".", qualifiedByName = "supportedLocales") + @Mapping(target = "defaultLocale", constant = DEFAULT_LOCALE) + @Mapping(target = "internationalizationEnabled", constant = INTERNATIONALIZATION_ENABLED) + @Mapping(target = "passwordPolicy", constant = PASSWORD_POLICY) + @Mapping(target = "actionTokenGeneratedByUserLifespan", constant = ACTION_TOKEN_GENERATED_BY_USER_LIFE_SPAN) + @Mapping(target = "smtpServer", source = "smtpServer", qualifiedByName = "smtpServer") + @Mapping(target = "roles.realm", source = "realmRoles") + RealmRepresentation update(@MappingTarget RealmRepresentation existingRealm, OzgCloudKeycloakRealmSpec spec); @Mapping(target = "name", source = "name") - RoleRepresentation map(OzgCloudKeycloakRealmSpec.RealmRole role); + RoleRepresentation map(OzgCloudKeycloakRealmSpec.RealmRole role); @Named("supportedLocales") default Set<String> mapPassword(OzgCloudKeycloakRealmSpec spec) { @@ -69,14 +82,14 @@ interface KeycloakRealmMapper { Map<String, String> smtpServer = new HashMap<>(); if (server != null) { - smtpServer.put("host", server.getHost()); - smtpServer.put("port", server.getPort()); - smtpServer.put("user", server.getUser()); - smtpServer.put("password", server.getPassword()); - smtpServer.put("starttls", server.getStarttls()); - smtpServer.put("auth", server.getAuth()); - smtpServer.put("from", server.getFrom()); - smtpServer.put("fromDisplayName", server.getFromDisplayName()); + smtpServer.put(KeycloakRealmSMTPServer.HOST_FIELD, server.getHost()); + smtpServer.put(KeycloakRealmSMTPServer.PORT_FIELD, server.getPort()); + smtpServer.put(KeycloakRealmSMTPServer.USER_FIELD, server.getUser()); + smtpServer.put(KeycloakRealmSMTPServer.PASSWORD_FIELD, server.getPassword()); + smtpServer.put(KeycloakRealmSMTPServer.STARTTLS_FIELD, server.getStarttls()); + smtpServer.put(KeycloakRealmSMTPServer.AUTH_FIELD, server.getAuth()); + smtpServer.put(KeycloakRealmSMTPServer.FROM_FIELD, server.getFrom()); + smtpServer.put(KeycloakRealmSMTPServer.FROM_DISPLAY_NAME_FIELD, server.getFromDisplayName()); } return smtpServer; diff --git a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconciler.java b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconciler.java index 7edc208537aec1d2afa2817c60ab98cb8eebb61f..c17da17a68ff9510cbd0a20831847e13df594b56 100644 --- a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconciler.java +++ b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconciler.java @@ -50,7 +50,7 @@ public class KeycloakRealmReconciler implements Reconciler<OzgCloudKeycloakRealm try { var realmName = resource.getMetadata().getNamespace(); - service.createRealm(resource.getSpec(), realmName); + service.createOrUpdateRealm(resource.getSpec(), realmName); resource.setStatus(OzgCloudKeycloakRealmStatus.builder().status(OzgCloudCustomResourceStatus.OK).message(null).build()); return UpdateControl.updateStatus(resource); 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 124f1d5dc65bcf9a39d05c279e8d8f4ac5b48150..5829cacba9f23de8c68a53ecf9667835453df742 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 @@ -23,10 +23,15 @@ */ package de.ozgcloud.operator.keycloak.realm; +import java.util.Objects; +import java.util.Optional; + import org.keycloak.admin.client.Keycloak; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.stereotype.Component; +import de.ozgcloud.operator.keycloak.KeycloakException; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -42,4 +47,26 @@ class KeycloakRealmRemoteService { public void deleteRealm(String realmName) { keycloak.realm(realmName).remove(); } + + public void updateRealm(RealmRepresentation realm) { + keycloak.realm(realm.getRealm()).update(realm); + } + + public Optional<RoleRepresentation> getRealmRole(String roleName, String realmName) { + return Optional.ofNullable(keycloak.realm(realmName)) + .orElseThrow(() -> new KeycloakException("Realm with Name " + realmName + " not found.")) + .roles() + .list() + .stream().filter(role -> Objects.equals(roleName, role.getName())) + .findFirst(); + } + + public void updateRealmRole(RoleRepresentation role, String realm) { + keycloak.realm(realm).roles().get(role.getName()).update(role); + } + + public void addRealmRole(RoleRepresentation role, String realm) { + keycloak.realm(realm).roles().create(role); + } + } 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 279a05573ba927a7b84b4f76166785b1fb2641e1..c92e75c364d25a34b9b99c6d678d7bec02d3426a 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 @@ -30,7 +30,9 @@ import org.springframework.stereotype.Component; import de.ozgcloud.operator.keycloak.KeycloakGenericRemoteService; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +@Log4j2 @RequiredArgsConstructor @Component class KeycloakRealmService { @@ -41,7 +43,7 @@ class KeycloakRealmService { private final KeycloakGenericRemoteService keycloakGenericRemoteService; - public void createRealm(OzgCloudKeycloakRealmSpec realm, String realmName) { + void createRealm(OzgCloudKeycloakRealmSpec realm, String realmName) { Optional.of(realm) .map(mapper::map) .map(realmRepresentation -> addRealmName(realmRepresentation, realmName)) @@ -49,6 +51,34 @@ class KeycloakRealmService { .ifPresent(remoteService::createRealm); } + public void createOrUpdateRealm(OzgCloudKeycloakRealmSpec realm, String realmName) { + keycloakGenericRemoteService.getRealmRepresentation(realmName) + .ifPresentOrElse(existingRealm -> updateRealm(existingRealm, realm), + () -> createRealm(realm, realmName)); + } + + void updateRealm(RealmRepresentation existingRealm, OzgCloudKeycloakRealmSpec spec) { + + try { + LOG.debug("{}: Updating existing realm...", existingRealm); + var realmRepresentation = mapper.update(existingRealm, spec); + remoteService.updateRealm(realmRepresentation); + } catch (Exception e) { + LOG.warn(existingRealm + ": Updating existing realm failed: ", e); + throw e; + } + addOrUpdateRealmRoles(spec, existingRealm.getRealm()); + + } + + void addOrUpdateRealmRoles(OzgCloudKeycloakRealmSpec spec, String realm) { + spec.getRealmRoles().forEach( + roleSpec -> remoteService.getRealmRole(roleSpec.getName(), realm) + .ifPresentOrElse( + existingRole -> remoteService.updateRealmRole(mapper.map(roleSpec), realm), + () -> remoteService.addRealmRole(mapper.map(roleSpec), realm))); + } + RealmRepresentation addRealmName(RealmRepresentation realm, String realmName) { realm.setRealm(realmName); return realm; diff --git a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/OzgCloudKeycloakRealmSpec.java b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/OzgCloudKeycloakRealmSpec.java index e68dd6ba3e90b8dc25587b0be191c95e02c3452f..d1332813f2dbe03f85a71f075a34eed4ceb230c0 100644 --- a/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/OzgCloudKeycloakRealmSpec.java +++ b/ozgcloud-keycloak-operator/src/main/java/de/ozgcloud/operator/keycloak/realm/OzgCloudKeycloakRealmSpec.java @@ -57,6 +57,22 @@ class OzgCloudKeycloakRealmSpec { @AllArgsConstructor static class KeycloakRealmSMTPServer { + public static final String USER_FIELD = "user"; + + public static final String PASSWORD_FIELD = "password"; + + public static final String HOST_FIELD = "host"; + + public static final String PORT_FIELD = "port"; + + public static final String STARTTLS_FIELD = "starttls"; + + public static final String AUTH_FIELD = "auth"; + + public static final String FROM_FIELD = "from"; + + public static final String FROM_DISPLAY_NAME_FIELD = "fromDisplayName"; + private String user; private String password; diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteServiceTest.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteServiceTest.java index e6b96ee346bb544e87367bd2ddf855ffd2268b28..26fe60961563fc06e02a8f0ae7ac5f368ddd8841 100644 --- a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteServiceTest.java +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/KeycloakGenericRemoteServiceTest.java @@ -43,6 +43,7 @@ import org.keycloak.admin.client.resource.RealmsResource; import org.keycloak.admin.client.resource.RolesResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -81,6 +82,8 @@ class KeycloakGenericRemoteServiceTest { private RolesResource rolesResource; @Mock private RoleRepresentation roleRepresentation; + @Mock + private RealmRepresentation realmRepresentation; @Nested class TestGetKeycloakClientById { @@ -189,4 +192,34 @@ class KeycloakGenericRemoteServiceTest { () -> service.getClientRole(UserRepresentationTestFactory.ROLE1, REAL_CLIENT_ID, REALM)); } } + + @Nested + class TestGetRealmRepresentation { + + @BeforeEach + void init() { + when(keycloak.realms()).thenReturn(realmsResource); + } + + @Test + void shouldGetRealmFromKeycloakWhenExist() { + when(keycloak.realm(REALM)).thenReturn(realmResource); + when(service.realmExists(REALM)).thenReturn(true); + when(realmResource.toRepresentation()).thenReturn(realmRepresentation); + + var realm = service.getRealmRepresentation(REALM); + + assertThat(realm).isNotEmpty().contains(realmRepresentation); + } + + @Test + void shouldNotGetRealmFromKeycloakWhenNotExist() { + + when(service.realmExists(REALM)).thenReturn(false); + + var realm = service.getRealmRepresentation(REALM); + + assertTrue(realm.isEmpty()); + } + } } diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapperTest.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapperTest.java index f36b39c8fc173d6391b7bd0b8107c428f6ff766f..bd951e01b9d51919cc258b7b5316589c18a0cc99 100644 --- a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapperTest.java +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmMapperTest.java @@ -25,91 +25,100 @@ package de.ozgcloud.operator.keycloak.realm; import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.RealmRepresentation; import org.mapstruct.factory.Mappers; import org.mockito.Spy; +import de.ozgcloud.operator.keycloak.realm.OzgCloudKeycloakRealmSpec.KeycloakRealmSMTPServer; + class KeycloakRealmMapperTest { @Spy private final KeycloakRealmMapper mapper = Mappers.getMapper(KeycloakRealmMapper.class); + private RealmRepresentation mapRealm() { + return mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + + } + @Test void shouldMapDisplayName() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getDisplayName()).isEqualTo(OzgCloudKeycloakRealmSpecTestFactory.DISPLAY_NAME); } @Test void shouldBeEnabled() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.isEnabled()).isTrue(); } @Test void shouldBeResetPasswordAllowed() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.isResetPasswordAllowed()).isTrue(); } @Test void shouldBeSupportedLocaleDe() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getSupportedLocales()).containsExactly("de"); } @Test void shouldBeDefaultLocaleDe() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getDefaultLocale()).isEqualTo("de"); } @Test void shouldBeInternationalizationEnabled() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.isInternationalizationEnabled()).isTrue(); } @Test void checkPasswordPolicy() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getPasswordPolicy()).isEqualTo("upperCase(1) and lowerCase(1) and length(8) and notUsername"); } @Test void shouldSetActionTokenGeneratedByUserLifespan() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getActionTokenGeneratedByUserLifespan()).isEqualTo(900); } @Test void shouldMapSmtpServer() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); - assertThat(mapped.getSmtpServer().size()).isEqualTo(8); + assertThat(mapped.getSmtpServer()).hasSize(8); } @Test void shouldContainSmtpServerKeysValues() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getSmtpServer()) - .containsEntry("user", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_USER) - .containsEntry("password", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_PASSWORD) - .containsEntry("host", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_HOST) - .containsEntry("port", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_PORT) - .containsEntry("starttls", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_STARTTLS) - .containsEntry("auth", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_AUTH) - .containsEntry("from", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_FROM) - .containsEntry("fromDisplayName", KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_FROM_DISPLAY_NAME); + .containsEntry(KeycloakRealmSMTPServer.USER_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_USER) + .containsEntry(KeycloakRealmSMTPServer.PASSWORD_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_PASSWORD) + .containsEntry(KeycloakRealmSMTPServer.HOST_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_HOST) + .containsEntry(KeycloakRealmSMTPServer.PORT_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_PORT) + .containsEntry(KeycloakRealmSMTPServer.STARTTLS_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_STARTTLS) + .containsEntry(KeycloakRealmSMTPServer.AUTH_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_AUTH) + .containsEntry(KeycloakRealmSMTPServer.FROM_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_FROM) + .containsEntry(KeycloakRealmSMTPServer.FROM_DISPLAY_NAME_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_FROM_DISPLAY_NAME); } @Test @@ -121,17 +130,124 @@ class KeycloakRealmMapperTest { @Test void shouldMapRealmRoles() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); assertThat(mapped.getRoles().getRealm()).hasSize(2); } @Test void shouldContainRealmRolesValues() { - var mapped = mapper.map(OzgCloudKeycloakRealmSpecTestFactory.create()); + var mapped = mapRealm(); var mappedRealmRoles = mapped.getRoles(); assertThat(mappedRealmRoles.getRealm().get(0).getName()).isEqualTo(OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_1); assertThat(mappedRealmRoles.getRealm().get(1).getName()).isEqualTo(OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_2); } + + @Nested + class TestUpdate { + + @Test + void shouldMapDisplayName() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getDisplayName()).isEqualTo(OzgCloudKeycloakRealmSpecTestFactory.DISPLAY_NAME); + } + + @Test + void shouldBeEnabled() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.isEnabled()).isTrue(); + } + + @Test + void shouldBeResetPasswordAllowed() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.isResetPasswordAllowed()).isTrue(); + } + + @Test + void shouldBeSupportedLocaleDe() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getSupportedLocales()).containsExactly("de"); + } + + @Test + void shouldBeDefaultLocaleDe() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getDefaultLocale()).isEqualTo("de"); + } + + @Test + void shouldBeInternationalizationEnabled() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.isInternationalizationEnabled()).isTrue(); + } + + @Test + void checkPasswordPolicy() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getPasswordPolicy()).isEqualTo("upperCase(1) and lowerCase(1) and length(8) and notUsername"); + } + + @Test + void shouldSetActionTokenGeneratedByUserLifespan() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getActionTokenGeneratedByUserLifespan()).isEqualTo(900); + } + + @Test + void shouldMapSmtpServer() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getSmtpServer()).hasSize(8); + } + + @Test + void shouldContainSmtpServerKeysValues() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getSmtpServer()) + .containsEntry(KeycloakRealmSMTPServer.USER_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_USER) + .containsEntry(KeycloakRealmSMTPServer.PASSWORD_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_PASSWORD) + .containsEntry(KeycloakRealmSMTPServer.HOST_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_HOST) + .containsEntry(KeycloakRealmSMTPServer.PORT_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_PORT) + .containsEntry(KeycloakRealmSMTPServer.STARTTLS_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_STARTTLS) + .containsEntry(KeycloakRealmSMTPServer.AUTH_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_AUTH) + .containsEntry(KeycloakRealmSMTPServer.FROM_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_FROM) + .containsEntry(KeycloakRealmSMTPServer.FROM_DISPLAY_NAME_FIELD, KeycloakRealmSmtpServerTestFactory.SMTP_SERVER_FROM_DISPLAY_NAME); + } + + @Test + void shouldMapWhenSmtpServerIsNull() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), + OzgCloudKeycloakRealmSpecTestFactory.createBuilder().smtpServer(null).build()); + + assertThat(mapped.getSmtpServer()).isEmpty(); + } + + @Test + void shouldMapRealmRoles() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + + assertThat(mapped.getRoles().getRealm()).hasSize(2); + } + + @Test + void shouldContainRealmRolesValues() { + var mapped = mapper.update(RealmRepresentationTestFactory.create(), OzgCloudKeycloakRealmSpecTestFactory.create()); + var mappedRealmRoles = mapped.getRoles(); + + assertThat(mappedRealmRoles.getRealm().get(0).getName()).isEqualTo(OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_1); + assertThat(mappedRealmRoles.getRealm().get(1).getName()).isEqualTo(OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_2); + } + + } } diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconcilerTest.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconcilerTest.java index 76e8fad81d7e513c4c24c68e8631c3d093b91cf1..06e758f079a0e46cf2b1ceba00381683a700258f 100644 --- a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconcilerTest.java +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/KeycloakRealmReconcilerTest.java @@ -23,8 +23,11 @@ */ package de.ozgcloud.operator.keycloak.realm; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -51,12 +54,12 @@ class KeycloakRealmReconcilerTest { class TestReconcile { @Test - void shouldCallServiceAddRealm() { + void shouldCallServiceCreateOrUpdateRealm() { OzgCloudKeycloakRealm realm = OzgCloudKeycloakRealmTestFactory.create(); reconciler.reconcile(realm, null); - verify(service).createRealm(realm.getSpec(), OzgCloudKeycloakRealmTestFactory.METADATA_NAMESPACE); + verify(service).createOrUpdateRealm(realm.getSpec(), OzgCloudKeycloakRealmTestFactory.METADATA_NAMESPACE); } @Test @@ -73,8 +76,6 @@ class KeycloakRealmReconcilerTest { assertThat(response.getResource().getStatus().getStatus()).isEqualTo(OzgCloudCustomResourceStatus.OK); } - - } @DisplayName("Reconciler Cleanup") 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 a8d6cbaeaed8cc71f1f8af633146df2a673c345d..0b20c33a139e924d42890b07d21cd3a962f99097 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 @@ -24,6 +24,12 @@ package de.ozgcloud.operator.keycloak.realm; 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; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -31,10 +37,15 @@ import org.junit.jupiter.api.Test; import org.keycloak.admin.client.Keycloak; 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.representations.idm.RoleRepresentation; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; +import de.ozgcloud.operator.keycloak.KeycloakException; + class KeycloakRealmRemoteServiceTest { @Spy @@ -50,6 +61,18 @@ class KeycloakRealmRemoteServiceTest { @Mock private RealmResource realmResource; + @Mock + private RolesResource rolesResource; + + @Mock + private RoleResource roleResource; + + @Mock + private RoleRepresentation roleRepresentation; + + private static final String REALM_NAME = RealmRepresentationTestFactory.NAME; + private static final String ROLE_NAME = OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_1; + @Nested class TestCreateRealm { @@ -69,18 +92,103 @@ class KeycloakRealmRemoteServiceTest { } @Nested - class TestDeleteRealm { + class TestUpdateRealm { + + @BeforeEach + void init() { + when(keycloak.realm(REALM_NAME)).thenReturn(realmResource); + } + + @Test + void shouldCallUpdateRealm() { + var realm = RealmRepresentationTestFactory.create(); - private static final String REALM_NAME = "TestRealmName"; + remoteService.updateRealm(realm); + + verify(realmResource).update(realm); + } + } + + @Nested + class TestDeleteRealm { @Test void shouldDeleteRealm() { when(keycloak.realm(REALM_NAME)).thenReturn(realmResource); - remoteService.deleteRealm(REALM_NAME); verify(realmResource).remove(); } } + + @Nested + class TestRealmRoles { + + @Nested + class TestAddRealmRole { + + @BeforeEach + void init() { + when(keycloak.realm(any())).thenReturn(realmResource); + when(realmResource.roles()).thenReturn(rolesResource); + } + + @Test + void shouldAddRealmRole() { + remoteService.addRealmRole(roleRepresentation, REALM_NAME); + + verify(rolesResource).create(roleRepresentation); + } + } + + @Nested + class TestUpdateRealmRole { + + @BeforeEach + void init() { + when(keycloak.realm(REALM_NAME)).thenReturn(realmResource); + when(realmResource.roles()).thenReturn(rolesResource); + when(rolesResource.get(ROLE_NAME)).thenReturn(roleResource); + when(roleRepresentation.getName()).thenReturn(ROLE_NAME); + + } + + @Test + void shouldUpdateRealmRole() { + remoteService.updateRealmRole(roleRepresentation, REALM_NAME); + + verify(roleResource).update(roleRepresentation); + } + } + + @Nested + class TestGetRealmRole { + + @BeforeEach + void init() { + when(keycloak.realm(REALM_NAME)).thenReturn(realmResource); + + } + + @Test + void shouldReturnRealmRole() { + when(realmResource.roles()).thenReturn(rolesResource); + when(rolesResource.list()).thenReturn(List.of(roleRepresentation)); + when(roleRepresentation.getName()).thenReturn(ROLE_NAME); + + Optional<RoleRepresentation> role = remoteService.getRealmRole(ROLE_NAME, REALM_NAME); + + assertThat(role).isPresent().contains(roleRepresentation); + } + + @Test + void shouldThrowOnRealmNotFound() { + when(keycloak.realm(REALM_NAME)).thenReturn(null); + + assertThrows(KeycloakException.class, + () -> remoteService.getRealmRole(ROLE_NAME, REALM_NAME)); + } + } + } } 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 529dd38008b6142b3e37ee6a02ac590ae3833124..5d2da035924be16afec0f64718db93129d0ced41 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 @@ -26,7 +26,10 @@ package de.ozgcloud.operator.keycloak.realm; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.keycloak.representations.idm.RealmRepresentation; @@ -35,11 +38,14 @@ import org.mockito.Mock; import org.mockito.Spy; import de.ozgcloud.operator.keycloak.KeycloakGenericRemoteService; +import de.ozgcloud.operator.keycloak.realm.OzgCloudKeycloakRealmSpec.RealmRole; class KeycloakRealmServiceTest { private static final OzgCloudKeycloakRealmSpec REALM = OzgCloudKeycloakRealmSpecTestFactory.create(); private static final String REALM_NAME = "TestRealmName"; + private static final String ROLE_NAME = OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_1; + private static final RealmRole ROLE = OzgCloudKeycloakRealmSpecTestFactory.ROLE1; @Spy @InjectMocks @@ -57,6 +63,107 @@ class KeycloakRealmServiceTest { @Mock private KeycloakGenericRemoteService keycloakGenericRemoteService; + @Nested + class TestCreateOrUpdateRealm { + + @Test + void shouldCallCreateRealmMethodIfNotExists() { + + when(keycloakGenericRemoteService.getRealmRepresentation(REALM_NAME)).thenReturn(Optional.empty()); + + service.createOrUpdateRealm(REALM, REALM_NAME); + + verify(service).createRealm(REALM, REALM_NAME); + } + + @Test + void shouldCallUpdateRealmIfAlreadyExists() { + var existingRealm = RealmRepresentationTestFactory.create(); + when(keycloakGenericRemoteService.getRealmRepresentation(REALM_NAME)).thenReturn(Optional.of(existingRealm)); + + service.createOrUpdateRealm(REALM, REALM_NAME); + + verify(service).updateRealm(existingRealm, REALM); + } + + @Test + void shouldCallGetRealmRepresentation() { + service.createOrUpdateRealm(REALM, REALM_NAME); + + verify(keycloakGenericRemoteService).getRealmRepresentation(REALM_NAME); + } + } + + @DisplayName("Update Realm") + @Nested + class TestUpdateRealm { + + @BeforeEach + void init() { + when(mapper.update(any(), any())).thenReturn(realmRepresentation); + } + + @Test + void shouldUpdateRealmIfExists() { + service.updateRealm(realmRepresentation, REALM); + + verify(remoteService).updateRealm(realmRepresentation); + } + + @Test + void shouldCallMapper() { + + service.updateRealm(realmRepresentation, REALM); + + verify(mapper).update(realmRepresentation, REALM); + } + + @Test + void shouldCallAddOrUpdateRealmRoles() { + + service.updateRealm(realmRepresentation, REALM); + + verify(service).addOrUpdateRealmRoles(REALM, realmRepresentation.getRealm()); + } + + } + + @DisplayName("Add or Update Realm Roles") + @Nested + class TestAddOrUpdateRealmRoles { + + @Test + void shouldCallRemoteServiceGetRole() { + + service.addOrUpdateRealmRoles(REALM, REALM_NAME); + + verify(remoteService).getRealmRole(ROLE_NAME, REALM_NAME); + } + + @Test + void shouldCreateRole() { + when(remoteService.getRealmRole(ROLE_NAME, REALM_NAME)).thenReturn(Optional.empty()); + var roleRepresentation = RealmRoleRepresentationTestFactory.create(); + when(mapper.map(ROLE)).thenReturn(roleRepresentation); + + service.addOrUpdateRealmRoles(REALM, REALM_NAME); + + verify(remoteService).addRealmRole(roleRepresentation, REALM_NAME); + } + + @Test + void shouldUpdateRole() { + var roleRepresentation = RealmRoleRepresentationTestFactory.create(); + when(mapper.map(ROLE)).thenReturn(roleRepresentation); + when(remoteService.getRealmRole(ROLE_NAME, REALM_NAME)).thenReturn(Optional.of(roleRepresentation)); + + service.addOrUpdateRealmRoles(REALM, REALM_NAME); + + verify(remoteService).updateRealmRole(roleRepresentation, REALM_NAME); + } + + } + @Nested class TestCreateRealm { diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/RealmRoleRepresentationTestFactory.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/RealmRoleRepresentationTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..288c07f76299e58b21bf0c4310e5ebd34aba6b2d --- /dev/null +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/realm/RealmRoleRepresentationTestFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 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.operator.keycloak.realm; + +import org.keycloak.representations.idm.RoleRepresentation; + +public class RealmRoleRepresentationTestFactory { + + public static RoleRepresentation create() { + var role = new RoleRepresentation(); + role.setName(OzgCloudKeycloakRealmSpecTestFactory.ROLE_NAME_1); + + return role; + } +} diff --git a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakLivelTest.java b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakLivelTest.java index fb6cfcdea02ce6af9173de162ee5cd28dcc668c6..11b81fc69c810883a5bd2a5fd72360dcbe61b3df 100644 --- a/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakLivelTest.java +++ b/ozgcloud-keycloak-operator/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakLivelTest.java @@ -79,6 +79,7 @@ class KeycloakLivelTest { userRemoteService.updateUser(u, TESTNAMESPACE); } + @SuppressWarnings("unused") private UserRepresentation createUser() { UserRepresentation u = new UserRepresentation(); u.setUsername(TESTUSERNAME); diff --git a/pom.xml b/pom.xml index a879d872c2bf681a1ee4e6062dfac51ca473a530..e912ec06599e740425c2343d82f2087b8665689b 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.1.8</version> + <version>3.2.6</version> <relativePath/> </parent>