From 773487396f3753ab66f5fdac656e76752b8e87ff Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Wed, 2 Jun 2021 11:26:09 +0200 Subject: [PATCH] OZG-400 implement searching user in keycloak --- goofy-server/pom.xml | 10 +++++ .../de/itvsh/goofy/common/ContextService.java | 2 +- .../common/datatypes/StringBasedValue.java | 4 -- .../itvsh/goofy/common/user/UserIdMapper.java | 15 +++++++ .../goofy/common/user/UserRemoteService.java | 12 ++++++ .../keycloak/KeycloakAdminConfiguration.java | 39 +++++++++++++++++++ .../keycloak/KeycloakAdminProperties.java | 22 +++++++++++ .../user/keycloak/KeycloakApiService.java | 29 ++++++++++++++ .../keycloak/KeycloakUserRemoteService.java | 31 +++++++++++++++ .../keycloak/UserRepresentationMapper.java | 18 +++++++++ .../src/main/resources/application.yml | 4 ++ .../goofy/common/ContextServiceTest.java | 2 +- .../keycloak/KeycloakApiFunctionalITCase.java | 38 ++++++++++++++++++ pom.xml | 18 ++++++--- 14 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminConfiguration.java create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminProperties.java create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiService.java create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakUserRemoteService.java create mode 100644 goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/UserRepresentationMapper.java create mode 100644 goofy-server/src/test/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiFunctionalITCase.java diff --git a/goofy-server/pom.xml b/goofy-server/pom.xml index 841b6bb0e2..12f10df6b7 100644 --- a/goofy-server/pom.xml +++ b/goofy-server/pom.xml @@ -63,10 +63,15 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> + <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-boot-starter</artifactId> </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-admin-client</artifactId> + </dependency> <!-- own projects --> <dependency> @@ -142,6 +147,11 @@ <groupId>com.thedeanda</groupId> <artifactId>lorem</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> </dependencies> <build> diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/ContextService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/ContextService.java index cfd6d28b69..3a73893ec8 100644 --- a/goofy-server/src/main/java/de/itvsh/goofy/common/ContextService.java +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/ContextService.java @@ -28,7 +28,7 @@ public class ContextService { var goofyUser = userService.getUser(); return GrpcUser.newBuilder() - .setId(goofyUser.getId().toStringValue()) + .setId(goofyUser.getId().toString()) .setName(goofyUser.getFullName()) .addAllRoles(goofyUser.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())) .build(); diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/datatypes/StringBasedValue.java b/goofy-server/src/main/java/de/itvsh/goofy/common/datatypes/StringBasedValue.java index a2e3d328ca..619b7a199c 100644 --- a/goofy-server/src/main/java/de/itvsh/goofy/common/datatypes/StringBasedValue.java +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/datatypes/StringBasedValue.java @@ -25,10 +25,6 @@ public abstract class StringBasedValue implements Serializable { this.value = value; } - public String toStringValue() { - return value; - } - @Override public String toString() { return value; diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java new file mode 100644 index 0000000000..f9349f3404 --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserIdMapper.java @@ -0,0 +1,15 @@ +package de.itvsh.goofy.common.user; + +import org.mapstruct.Mapper; + +@Mapper +public interface UserIdMapper { + + default UserId fromString(String userId) { + return UserId.from(userId); + } + + default String toString(UserId userId) { + return userId.toString(); + } +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java new file mode 100644 index 0000000000..f9e68ee8f1 --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java @@ -0,0 +1,12 @@ +package de.itvsh.goofy.common.user; + +import java.util.Optional; +import java.util.stream.Stream; + +public interface UserRemoteService { + + Optional<GoofyUser> findUser(UserId userId); + + Stream<GoofyUser> findUserProfile(String searchBy, int limit); + +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminConfiguration.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminConfiguration.java new file mode 100644 index 0000000000..b8e75f9ed3 --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminConfiguration.java @@ -0,0 +1,39 @@ +package de.itvsh.goofy.common.user.keycloak; + +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +class KeycloakAdminConfiguration { + + private static final String CLIENT = "admin-cli"; + + @Autowired + private KeycloakSpringBootConfigResolver configResolver; + @Autowired + private KeycloakAdminProperties adminProperties; + + @Bean + public KeycloakDeployment keycloakDeployment() { + return configResolver.resolve(null); + } + + @Bean + public Keycloak keycloak() { + var deployment = this.keycloakDeployment(); + String realm = deployment.getRealm(); + + return KeycloakBuilder.builder() + .serverUrl(deployment.getAuthServerBaseUrl()) + .realm(realm) + .username(adminProperties.getUser()) + .password(adminProperties.getPassword()) + .clientId(CLIENT) + .build(); + } +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminProperties.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminProperties.java new file mode 100644 index 0000000000..71754dd52d --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakAdminProperties.java @@ -0,0 +1,22 @@ +package de.itvsh.goofy.common.user.keycloak; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Valid +@Configuration +@ConfigurationProperties(prefix = "goofy.keycloak.api") +class KeycloakAdminProperties { + @NotBlank + private String user; + @NotBlank + private String password; +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiService.java new file mode 100644 index 0000000000..58433f0965 --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiService.java @@ -0,0 +1,29 @@ +package de.itvsh.goofy.common.user.keycloak; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.itvsh.goofy.common.user.UserId; + +@Service +class KeycloakApiService { + + @Autowired + private Keycloak keycloak; + @Autowired + private KeycloakDeployment deployment; + + public Optional<UserRepresentation> findUser(UserId userId) { + return Optional.ofNullable(keycloak.realm(deployment.getRealm()).users().get(userId.toString()).toRepresentation()); + } + + public Stream<UserRepresentation> findUserProfile(String searchBy, int limit) { + return keycloak.realm(deployment.getRealm()).users().search(searchBy, 0, limit).stream(); + } +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakUserRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakUserRemoteService.java new file mode 100644 index 0000000000..b3f1169d25 --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/KeycloakUserRemoteService.java @@ -0,0 +1,31 @@ +package de.itvsh.goofy.common.user.keycloak; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.itvsh.goofy.common.user.GoofyUser; +import de.itvsh.goofy.common.user.UserId; +import de.itvsh.goofy.common.user.UserRemoteService; + +@Service +class KeycloakUserRemoteService implements UserRemoteService { + + @Autowired + private KeycloakApiService apiService; + @Autowired + private UserRepresentationMapper userMapper; + + @Override + public Optional<GoofyUser> findUser(UserId userId) { + return apiService.findUser(userId).map(userMapper::toGoofyUser); + } + + @Override + public Stream<GoofyUser> findUserProfile(String searchBy, int limit) { + return apiService.findUserProfile(searchBy, limit).map(userMapper::toGoofyUser); + } + +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/UserRepresentationMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/UserRepresentationMapper.java new file mode 100644 index 0000000000..d3c1df681f --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/keycloak/UserRepresentationMapper.java @@ -0,0 +1,18 @@ +package de.itvsh.goofy.common.user.keycloak; + +import org.keycloak.representations.idm.UserRepresentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import de.itvsh.goofy.common.user.GoofyUser; +import de.itvsh.goofy.common.user.UserIdMapper; + +@Mapper(uses = UserIdMapper.class) +interface UserRepresentationMapper { + + // TODO map roles + @Mapping(target = "authorities", ignore = true) + @Mapping(target = "authority", ignore = true) + @Mapping(target = "userName", source = "username") + GoofyUser toGoofyUser(UserRepresentation userRep); +} diff --git a/goofy-server/src/main/resources/application.yml b/goofy-server/src/main/resources/application.yml index 908e7b0925..89eb3f07a2 100644 --- a/goofy-server/src/main/resources/application.yml +++ b/goofy-server/src/main/resources/application.yml @@ -26,6 +26,10 @@ management: goofy: production: true + keycloak: + api: + user: goofyApiUser + password: abs9354.3Yds grpc: diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/ContextServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/ContextServiceTest.java index 29781b0aad..dee597cc09 100644 --- a/goofy-server/src/test/java/de/itvsh/goofy/common/ContextServiceTest.java +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/ContextServiceTest.java @@ -66,7 +66,7 @@ class ContextServiceTest { void shoultHaveUserId() { var context = service.createCallContext(); - assertThat(context.getUser()).extracting(GrpcUser::getId).isEqualTo(UserTestFactory.ID.toStringValue()); + assertThat(context.getUser()).extracting(GrpcUser::getId).isEqualTo(UserTestFactory.ID.toString()); } @Test diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiFunctionalITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiFunctionalITCase.java new file mode 100644 index 0000000000..5262344b75 --- /dev/null +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/keycloak/KeycloakApiFunctionalITCase.java @@ -0,0 +1,38 @@ +package de.itvsh.goofy.common.user.keycloak; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("remotekc") +class KeycloakApiFunctionalITCase { + + private static final String REALM = "sh-kiel-dev"; + private static final String TEST_USER_NAME = "beate"; + + @Autowired + private Keycloak keycloak; + + @Test + void shouldFindTestUser() { + List<UserRepresentation> users = keycloak.realm(REALM).users().search(TEST_USER_NAME); + + assertThat(users).hasSize(1); + } + + @Test + void shouldFindTestUserByPartialName() { + List<UserRepresentation> users = keycloak.realm(REALM).users().search(TEST_USER_NAME.substring(1, 4)); + + assertThat(users).hasSize(1); + } + +} diff --git a/pom.xml b/pom.xml index 1b45cd0bca..521ebdffde 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>11</java.version> - + <pluto.version>${project.version}</pluto.version> <spring.boot.version>2.4.5</spring.boot.version> @@ -27,7 +27,7 @@ <spring-admin.version>2.3.1</spring-admin.version> <mapstruct.version>1.4.1.Final</mapstruct.version> <commons-io.version>2.8.0</commons-io.version> - <keycloak-adapter.version>12.0.4</keycloak-adapter.version> + <keycloak-adapter.version>13.0.1</keycloak-adapter.version> <lorem.version>2.1</lorem.version> @@ -50,6 +50,7 @@ <type>pom</type> <scope>import</scope> </dependency> + <!-- keycloak --> <dependency> <groupId>org.keycloak.bom</groupId> @@ -58,6 +59,11 @@ <type>pom</type> <scope>import</scope> </dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-admin-client</artifactId> + <version>${keycloak-adapter.version}</version> + </dependency> <dependency> <groupId>de.codecentric</groupId> @@ -147,10 +153,10 @@ </plugin> <plugin> - <groupId>org.sonarsource.scanner.maven</groupId> - <artifactId>sonar-maven-plugin</artifactId> - <version>${sonarqube.version}</version> - </plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>${sonarqube.version}</version> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> -- GitLab