From 6a4be92dc4edb7d820c18a55c1e35239e66fb1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20K=C3=A9pes?= <2853992+nyuuyn@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:21:24 +0200 Subject: [PATCH] Closes #2348 - adds feature to request the current user via /users?currentUser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kálmán Képes <2853992+nyuuyn@users.noreply.github.com> --- .../rest/util/QueryParamsValidator.java | 2 +- .../pro/taskana/user/rest/UserController.java | 32 ++++++- .../user/rest/UserControllerIntTest.java | 88 +++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/QueryParamsValidator.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/QueryParamsValidator.java index f42b208e93..10b7b0726a 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/QueryParamsValidator.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/QueryParamsValidator.java @@ -38,7 +38,7 @@ public static void validateParams(HttpServletRequest request, Class... filter checkExactParam(request, "owner-is-null"); } - private static void checkExactParam(HttpServletRequest request, String queryParameter) { + public static void checkExactParam(HttpServletRequest request, String queryParameter) { String queryString = request.getQueryString(); boolean containParam = queryString != null && queryString.contains(queryParameter); if (containParam) { diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/user/rest/UserController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/user/rest/UserController.java index 94afb387c0..063d0068a9 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/user/rest/UserController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/user/rest/UserController.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.List; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; @@ -18,7 +19,9 @@ import org.springframework.web.bind.annotation.RestController; import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.NotAuthorizedException; +import pro.taskana.common.api.security.CurrentUserContext; import pro.taskana.common.rest.RestEndpoints; +import pro.taskana.common.rest.util.QueryParamsValidator; import pro.taskana.user.api.UserService; import pro.taskana.user.api.exceptions.UserAlreadyExistException; import pro.taskana.user.api.exceptions.UserNotFoundException; @@ -27,6 +30,8 @@ import pro.taskana.user.rest.models.UserCollectionRepresentationModel; import pro.taskana.user.rest.models.UserRepresentationModel; +import javax.servlet.http.HttpServletRequest; + /** Controller for all {@linkplain User} related endpoints. */ @RestController @EnableHypermediaSupport(type = HypermediaType.HAL) @@ -34,10 +39,15 @@ public class UserController { private final UserService userService; private final UserRepresentationModelAssembler userAssembler; + private final CurrentUserContext currentUserContext; + @Autowired - UserController(UserService userService, UserRepresentationModelAssembler userAssembler) { + UserController(UserService userService, + UserRepresentationModelAssembler userAssembler, + CurrentUserContext currentUserContext) { this.userService = userService; this.userAssembler = userAssembler; + this.currentUserContext = currentUserContext; } /** @@ -54,24 +64,38 @@ public class UserController { public ResponseEntity getUser(@PathVariable String userId) throws UserNotFoundException, InvalidArgumentException { User user = userService.getUser(userId); - return ResponseEntity.ok(userAssembler.toModel(user)); } /** * This endpoint retrieves multiple Users. If a userId can't be found in the database it will be * ignored. If none of the given userIds is valid, the returned list will be empty. + * If currentUser is set, the current User from the context will be retrieved as well * * @title Get multiple Users * @param userIds the ids of the requested Users + * @param currentUser Indicates whether to fetch the current user or not as well * @return the requested Users * @throws InvalidArgumentException if the userIds are null or empty + * @throws UserNotFoundException if the current User was not found */ @GetMapping(RestEndpoints.URL_USERS) @Transactional(readOnly = true, rollbackFor = Exception.class) public ResponseEntity getUsers( - @RequestParam(name = "user-id") String[] userIds) throws InvalidArgumentException { - List users = userService.getUsers(new HashSet<>(List.of(userIds))); + HttpServletRequest request, + @RequestParam(name = "user-id", required = false) String[] userIds, + @RequestParam(name = "current-user", required = false) String currentUser) + throws InvalidArgumentException, UserNotFoundException { + Set users = new HashSet<>(); + + if (userIds != null) { + users.addAll(userService.getUsers(new HashSet<>(List.of(userIds)))); + } + + if (currentUser != null) { + QueryParamsValidator.checkExactParam(request, "current-user"); + users.add(userService.getUser(this.currentUserContext.getUserid())); + } return ResponseEntity.ok(userAssembler.toTaskanaCollectionModel(users)); } diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/UserControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/UserControllerIntTest.java index 410f72dbab..031a41a7c9 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/UserControllerIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/rest/UserControllerIntTest.java @@ -63,6 +63,94 @@ void should_ReturnExistingUsers() throws Exception { .containsExactlyInAnyOrder("Max", "Elena"); } + @Test + void should_ReturnCurrentUser() { + String url = restHelper.toUrl(RestEndpoints.URL_USERS) + "?current-user"; + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("teamlead-1")); + + ResponseEntity response = + TEMPLATE.exchange( + url, + HttpMethod.GET, + auth, + ParameterizedTypeReference.forType(UserCollectionRepresentationModel.class)); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().getContent()).hasSize(1); + assertThat(response.getBody().getContent()).extracting("userId").containsExactly("teamlead-1"); + } + + @Test + void should_ReturnExceptionCurrentUserWithBadValue() { + String url = restHelper.toUrl(RestEndpoints.URL_USERS) + "?current-user=asd"; + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("teamlead-1")); + + ThrowingCallable httpCall = + () -> TEMPLATE.exchange( + url, + HttpMethod.GET, + auth, + ParameterizedTypeReference.forType(UserCollectionRepresentationModel.class)); + assertThatThrownBy(httpCall) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void should_ReturnExceptionCurrentUserWithEmptyValue() { + String url = restHelper.toUrl(RestEndpoints.URL_USERS) + "?current-user="; + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("teamlead-1")); + + ThrowingCallable httpCall = + () -> TEMPLATE.exchange( + url, + HttpMethod.GET, + auth, + ParameterizedTypeReference.forType(UserCollectionRepresentationModel.class)); + assertThatThrownBy(httpCall) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void should_ReturnOnlyCurrentUserWhileUsingUserIds() { + String url = restHelper.toUrl(RestEndpoints.URL_USERS) + "?current-user&user-id=teamlead-1"; + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("teamlead-1")); + + ResponseEntity response = + TEMPLATE.exchange( + url, + HttpMethod.GET, + auth, + ParameterizedTypeReference.forType(UserCollectionRepresentationModel.class)); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().getContent()).hasSize(1); + assertThat(response.getBody().getContent()).extracting("userId").containsExactly("teamlead-1"); + } + + @Test + void should_ReturnExistingUsersAndCurrentUser() throws Exception { + String url = restHelper.toUrl(RestEndpoints.URL_USERS) + + "?user-id=user-1-1&user-id=USER-1-2¤t-user"; + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("teamlead-1")); + + ResponseEntity responseEntity = + TEMPLATE.exchange( + url, + HttpMethod.GET, + auth, + ParameterizedTypeReference.forType(UserCollectionRepresentationModel.class)); + UserCollectionRepresentationModel response = responseEntity.getBody(); + assertThat(response).isNotNull(); + assertThat(response.getContent()).hasSize(3); + assertThat(response.getContent()) + .extracting("userId") + .containsExactlyInAnyOrder("user-1-1", "user-1-2", "teamlead-1"); + } + @Test void should_ReturnExistingUsers_When_ParameterContainsDuplicateAndInvalidIds() throws Exception { // also testing different query parameter format