diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/auth/TokenDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/auth/TokenDto.java index cf3399dbe9a865c5b741426c40bb854fe2b84acf..1ef8ec88f2f3df71a47d4b74b87513537c818582 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/auth/TokenDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/auth/TokenDto.java @@ -16,11 +16,13 @@ import javax.validation.constraints.NotNull; public class TokenDto { @NotBlank + @ToString.Exclude @JsonProperty("access_token") @Schema(example = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJBbFdvalRsa1dBSVVoYTJLWjFvOEluWEtNbVAzUTg0STZiMFFHYkR6aEpvIn0.eyJleHAiOjE2ODAyNjgyNjgsImlhdCI6MTY4MDI2ODIwOCwianRpIjoiNjkwNjRlNTQtODNhNS00NGYxLWE3OTItNWFjOWU4OTA5YTlkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI4MjQ2OWMyMS0yYjNjLTRmMDctODg1Yi1hMzViMGQ5YTJhNjYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiNDQ1ODA3ZWUtMjg3Ni00NjFkLWE4ZjMtNGQyN2IzMGMyMWZhIiwiYWNyIjoiMSIsInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6IjQ0NTgwN2VlLTI4NzYtNDYxZC1hOGYzLTRkMjdiMzBjMjFmYSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiZmRhIn0.IOQxqWvlPDV9WuFOeLVG-ayexbK8OqylPABghEMSbMpmNlQhSAjbjaMY31uU-uADZRHB-mC8bmRS5PoWNtanuhz0lORDCeissFsbv0UL9Q42CaxG75vFAAD5WsdIHIr-dtEjEiXYtu-qwdg83griAUeO119TTdgldyPxo4jWzNw0ui6W7r4LqP4fSk31iJfxR5urgs5k6Ctzg-fXCORT31-nKz_YJQwLoPO9j4afX_1mnCXY5qFGMSrmPKzB0CArZfUpa_4nqt4Y768yOC3gigAyCjXtvXKkgCmARPSRjERGDdTb6SGbAwRDiVHVy9wy7XZwOcCFMEra9H7mV0Mx2A") private String accessToken; @NotBlank + @ToString.Exclude @JsonProperty("refresh_token") @Schema(example = "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4YTczZGYwZS03NzMwLTRiZDEtOGVhOC1mZTdjZWViNmMxYWMifQ.eyJleHAiOjE2ODAyNzAwMDgsImlhdCI6MTY4MDI2ODIwOCwianRpIjoiNWYyNDIwNDItNmJmZi00ZTQ2LTg2NTAtNDBhY2E3YjVkZjMyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsInN1YiI6IjgyNDY5YzIxLTJiM2MtNGYwNy04ODViLWEzNWIwZDlhMmE2NiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiNDQ1ODA3ZWUtMjg3Ni00NjFkLWE4ZjMtNGQyN2IzMGMyMWZhIiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiNDQ1ODA3ZWUtMjg3Ni00NjFkLWE4ZjMtNGQyN2IzMGMyMWZhIn0.-GltWGkIaKUJ4AqRYnGHblTr0ygZm2CsRQB6zz5ePm4") private String refreshToken; diff --git a/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java b/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java index 8bdb18dc09783d6ac2be5882629f353caf27b367..485dcccce4861baed02335cc8de4e05248631ea2 100644 --- a/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java +++ b/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java @@ -10,6 +10,7 @@ import io.micrometer.core.annotation.Timed; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -57,7 +58,8 @@ public class UserEndpoint { log.debug("endpoint create a user, data={}", data); final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data)); log.trace("create user resulted in dto {}", dto); - return ResponseEntity.ok(dto); + return ResponseEntity.status(HttpStatus.CREATED) + .body(dto); } } diff --git a/fda-user-service/rest-service/src/main/resources/application-local.yml b/fda-user-service/rest-service/src/main/resources/application-local.yml index 29e2dddbdfa32c50ab327bc3bc2fb1128d920a79..53c4a9a92ff3de564e8c168dd819675e0b5a92a1 100644 --- a/fda-user-service/rest-service/src/main/resources/application-local.yml +++ b/fda-user-service/rest-service/src/main/resources/application-local.yml @@ -31,7 +31,7 @@ server: port: 9098 ssl: enabled: true - key-alias: server + key-alias: user-service key-store: "./server.keystore" key-store-type: jks key-store-password: password @@ -51,7 +51,7 @@ eureka: client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/ fda: ready.path: ./ready - gateway.endpoint: https://localhost:9095 + gateway.endpoint: https://gateway-service:9095 keycloak: username: fda password: fda diff --git a/fda-user-service/services/src/main/java/at/tuwien/auth/AdminToken.java b/fda-user-service/services/src/main/java/at/tuwien/auth/AdminToken.java new file mode 100644 index 0000000000000000000000000000000000000000..3cbf74ae586af49abc7f75f1d645ef6825d9ba01 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/auth/AdminToken.java @@ -0,0 +1,21 @@ +package at.tuwien.auth; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminToken { + + private static AdminToken instance = null; + + private String token; + + public static synchronized AdminToken getInstance() { + if (instance == null) { + instance = new AdminToken(); + } + return instance; + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/config/GatewayConfig.java b/fda-user-service/services/src/main/java/at/tuwien/config/GatewayConfig.java index fa6ef35e180a6f4ed99b90269cdb4f1364cdeec0..bdede3578a04db7a93bb8808267cca5f62a8c2f1 100644 --- a/fda-user-service/services/src/main/java/at/tuwien/config/GatewayConfig.java +++ b/fda-user-service/services/src/main/java/at/tuwien/config/GatewayConfig.java @@ -1,6 +1,8 @@ package at.tuwien.config; +import at.tuwien.mapper.AuthenticationMapper; import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,10 +22,18 @@ public class GatewayConfig { @Value("${fda.keycloak.password}") private String keycloakPassword; + private final AuthenticationMapper authenticationMapper; + + @Autowired + public GatewayConfig(AuthenticationMapper authenticationMapper) { + this.authenticationMapper = authenticationMapper; + } + @Bean public RestTemplate restTemplate() { final RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(gatewayEndpoint)); + restTemplate.getMessageConverters().add(authenticationMapper.mappingJackson2HttpMessageConverter()); return restTemplate; } } diff --git a/fda-user-service/services/src/main/java/at/tuwien/gateway/impl/GatewayServiceGatewayImpl.java b/fda-user-service/services/src/main/java/at/tuwien/gateway/impl/GatewayServiceGatewayImpl.java index 159a07460d68f86fadb2227fefbece480a2921e7..e3b7fc239af236a82d9867025b0872938aa47bbf 100644 --- a/fda-user-service/services/src/main/java/at/tuwien/gateway/impl/GatewayServiceGatewayImpl.java +++ b/fda-user-service/services/src/main/java/at/tuwien/gateway/impl/GatewayServiceGatewayImpl.java @@ -32,17 +32,19 @@ public class GatewayServiceGatewayImpl implements GatewayServiceGateway { @Override public TokenDto getToken() throws RemoteUnavailableException { final HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED.toString()); - final MultiValueMap<String, String> payload = new LinkedMultiValueMap<>(); - payload.add("username", gatewayConfig.getKeycloakUsername()); - payload.add("password", gatewayConfig.getKeycloakPassword()); - payload.add("grant_type", "password"); - payload.add("client_id", "admin-cli"); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + final MultiValueMap<String, String> data = new LinkedMultiValueMap<>(); + data.set("username", gatewayConfig.getKeycloakUsername()); + data.set("password", gatewayConfig.getKeycloakPassword()); + data.set("grant_type", "password"); + data.set("client_id", "admin-cli"); final String url = "/api/auth/realms/master/protocol/openid-connect/token"; log.debug("call authentication service {}", url); + log.trace("headers: {}", headers); + log.trace("data: {}", data); final ResponseEntity<TokenDto> response; try { - response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(payload, headers), TokenDto.class); + response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(data, headers), TokenDto.class); } catch (ResourceAccessException | HttpServerErrorException.ServiceUnavailable e) { log.error("Failed to obtain admin token: {}", e.getMessage()); throw new RemoteUnavailableException("Failed to obtain admin token", e); @@ -57,9 +59,10 @@ public class GatewayServiceGatewayImpl implements GatewayServiceGateway { @Override public void createUser(String token, CreateUserDto data) throws RemoteUnavailableException { final HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Type", MediaType.APPLICATION_JSON.toString()); - headers.add("Accept", MediaType.APPLICATION_JSON.toString()); - headers.add("Authorization", "Bearer: " + token); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); + log.trace("headers: {}", headers); + log.trace("data: {}", data); final ResponseEntity<Void> response; try { response = restTemplate.exchange("/api/auth/admin/realms/dbrepo/users", HttpMethod.POST, new HttpEntity<>(data, headers), Void.class); diff --git a/fda-user-service/services/src/main/java/at/tuwien/listener/AdminTokenScheduler.java b/fda-user-service/services/src/main/java/at/tuwien/listener/AdminTokenScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..987c3e5668bee222fbd2a8449e4cd95571b6081b --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/listener/AdminTokenScheduler.java @@ -0,0 +1,32 @@ +package at.tuwien.listener; + +import at.tuwien.api.auth.TokenDto; +import at.tuwien.auth.AdminToken; +import at.tuwien.exception.RemoteUnavailableException; +import at.tuwien.gateway.GatewayServiceGateway; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +public class AdminTokenScheduler { + + private final AdminToken adminToken; + private final GatewayServiceGateway gatewayServiceGateway; + + @Autowired + public AdminTokenScheduler(GatewayServiceGateway gatewayServiceGateway) { + this.gatewayServiceGateway = gatewayServiceGateway; + this.adminToken = AdminToken.getInstance(); + } + + @Scheduled(fixedRate = 1000 * 60 * 3) + public void retrieveAdminToken() throws RemoteUnavailableException { + final TokenDto tokenDto = gatewayServiceGateway.getToken(); + log.trace("retrieved new admin token: {}", tokenDto); + adminToken.setToken(tokenDto.getAccessToken()); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/mapper/AuthenticationMapper.java b/fda-user-service/services/src/main/java/at/tuwien/mapper/AuthenticationMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..8e97787ee770caeb989717581e7ba01939f8c827 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/mapper/AuthenticationMapper.java @@ -0,0 +1,19 @@ +package at.tuwien.mapper; + +import org.mapstruct.Mapper; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +import java.util.Collections; + +@Mapper(componentModel = "spring") +public interface AuthenticationMapper { + + org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AuthenticationMapper.class); + + default MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { + final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); + mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED)); + return mappingJackson2HttpMessageConverter; + } +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java b/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java index 3a60fc1c3df990bb9cdf524fe62db5d4eee73c65..308cda436322a23d4509a10e7630dea8c0775065 100644 --- a/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java +++ b/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java @@ -1,6 +1,7 @@ package at.tuwien.mapper; import at.tuwien.api.auth.CreateUserDto; +import at.tuwien.api.auth.CredentialDto; import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.user.GrantedAuthorityDto; import at.tuwien.api.user.UserBriefDto; @@ -11,6 +12,8 @@ import org.mapstruct.Mapper; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import java.util.List; + @Mapper(componentModel = "spring") public interface UserMapper { @@ -22,7 +25,18 @@ public interface UserMapper { UserBriefDto userToUserBriefDto(User data); - CreateUserDto signupRequestDtoToCreateUserDto(SignupRequestDto data); + default CreateUserDto signupRequestDtoToCreateUserDto(SignupRequestDto data) { + return CreateUserDto.builder() + .username(data.getUsername()) + .email(data.getEmail()) + .enabled(true) + .credentials(List.of(CredentialDto.builder() + .temporary(false) + .type("password") + .value(data.getPassword()) + .build())) + .build(); + } default GrantedAuthority grantedAuthorityDtoToGrantedAuthority(GrantedAuthorityDto data) { final GrantedAuthority authority = new SimpleGrantedAuthority(data.getAuthority()); diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java b/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java index 177ce3f4534b1853647f6f4fd1db510bfaf5a768..b539ae05e8199ba921cf94628fba805ba2651ab4 100644 --- a/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java +++ b/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java @@ -2,7 +2,7 @@ package at.tuwien.service.impl; import at.tuwien.api.auth.CreateUserDto; import at.tuwien.api.auth.SignupRequestDto; -import at.tuwien.api.auth.TokenDto; +import at.tuwien.auth.AdminToken; import at.tuwien.entities.user.User; import at.tuwien.exception.RemoteUnavailableException; import at.tuwien.exception.UserNotFoundException; @@ -21,6 +21,7 @@ import java.util.Optional; @Service public class UserServiceImpl implements UserService { + private final AdminToken adminToken; private final UserMapper userMapper; private final UserRepository userRepository; private final GatewayServiceGateway authenticationServiceGateway; @@ -28,6 +29,7 @@ public class UserServiceImpl implements UserService { @Autowired public UserServiceImpl(UserMapper userMapper, UserRepository userRepository, GatewayServiceGateway authenticationServiceGateway) { + this.adminToken = AdminToken.getInstance(); this.userMapper = userMapper; this.userRepository = userRepository; this.authenticationServiceGateway = authenticationServiceGateway; @@ -50,10 +52,8 @@ public class UserServiceImpl implements UserService { @Override public User create(SignupRequestDto data) throws RemoteUnavailableException, UserNotFoundException { - final TokenDto dto = authenticationServiceGateway.getToken(); - log.debug("obtained authentication token"); final CreateUserDto userDto = userMapper.signupRequestDtoToCreateUserDto(data); - authenticationServiceGateway.createUser(dto.getAccessToken(), userDto); + authenticationServiceGateway.createUser(adminToken.getToken(), userDto); final Optional<User> optional = userRepository.findByUsername(data.getUsername()); if (optional.isEmpty()) { /* should never occur */