diff --git a/rest/taskana-rest-spring/src/docs/asciidoc/rest-api.adoc b/rest/taskana-rest-spring/src/docs/asciidoc/rest-api.adoc index 26b7d2c921..493b8b364a 100644 --- a/rest/taskana-rest-spring/src/docs/asciidoc/rest-api.adoc +++ b/rest/taskana-rest-spring/src/docs/asciidoc/rest-api.adoc @@ -11,6 +11,9 @@ Whenever a parameter is a complex type, the attributes of the value-object can b For example, a complex parameter with the name "complex-query-param" and attributes "attribute1" and "attribute2" would be specified in the following way: + complex-query-param={"attribute1":"value1","attribute2":"value2"} +Whenever a parameter is a value-less type (e.g owner-is-null and current-user) it is expected to be defined without a value, +i.e., it should be specified as ?parameter and not ?parameter= or ?parameter=someValue + === Hypermedia Support NOTE: HATEOAS support is still in development. 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..d82dc52ad5 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,8 @@ import java.util.HashSet; import java.util.List; +import java.util.Set; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; @@ -18,7 +20,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; @@ -34,10 +38,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 +63,39 @@ 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 request the HttpServletRequest of the request itself * @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