From 556f41a53d252348025ae1c65b20ec926d33fcae Mon Sep 17 00:00:00 2001 From: rogi1609 Date: Thu, 28 Jul 2016 16:44:27 +0200 Subject: [PATCH] Task download restriction When participating in a challenge, there are several tasks/levels. New tasks can only be shown respectively downloaded when the previous challenge was submitted and produced the correct results. Previously, this was just handled on the client side and users could have downloaded the task description of the next level by calling the corresponding API. This is not possible anymore as the permission to download the file is now checked server-side. This is done by setting a session attribute named "CURRENT_CHALLENGE" which is set when the challenge is viewed and allows the server to quickly check in what level the user is. --- .../server/core/service/ResultService.java | 21 +++--- .../server/core/service/TaskService.java | 53 +++++++++++--- .../server/core/service/TaskServiceTest.java | 71 ++++++++++++++++--- .../rest/controller/TaskController.java | 6 +- 4 files changed, 120 insertions(+), 31 deletions(-) diff --git a/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/ResultService.java b/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/ResultService.java index 839385b7..98546a9f 100644 --- a/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/ResultService.java +++ b/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/ResultService.java @@ -10,16 +10,12 @@ import uno.cod.platform.server.core.dto.user.UserShortShowDto; import uno.cod.platform.server.core.exception.CodunoIllegalArgumentException; import uno.cod.platform.server.core.mapper.ResultMapper; -import uno.cod.platform.server.core.repository.ChallengeRepository; -import uno.cod.platform.server.core.repository.ParticipationRepository; -import uno.cod.platform.server.core.repository.ResultRepository; +import uno.cod.platform.server.core.repository.*; +import javax.servlet.http.HttpSession; import javax.transaction.Transactional; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; +import java.util.*; @Service @Transactional @@ -28,14 +24,21 @@ public class ResultService { private final ChallengeRepository challengeRepository; private final TaskScheduler taskScheduler; private final ParticipationRepository participationRepository; + private final HttpSession httpSession; + + public static final String CURRENT_CHALLENGE = "CURRENT_CHALLENGE"; @Autowired public ResultService(ResultRepository repository, - ChallengeRepository challengeRepository, TaskScheduler taskScheduler, ParticipationRepository participationRepository) { + ChallengeRepository challengeRepository, + TaskScheduler taskScheduler, + ParticipationRepository participationRepository, + HttpSession httpSession) { this.repository = repository; this.challengeRepository = challengeRepository; this.taskScheduler = taskScheduler; this.participationRepository = participationRepository; + this.httpSession = httpSession; } public ResultShowDto save(UUID challengeId, User user) { @@ -81,6 +84,7 @@ public ResultShowDto save(UUID challengeId, User user) { repository.save(r); }, setFinished); + httpSession.setAttribute(CURRENT_CHALLENGE, challenge.getId()); return ResultMapper.map(result); } @@ -111,6 +115,7 @@ public ResultInfoDto getResultInfoForUserAndChallenge(UUID userId, UUID challeng if (result == null) { return null; } + httpSession.setAttribute(CURRENT_CHALLENGE, challengeId); return new ResultInfoDto(result); } } diff --git a/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/TaskService.java b/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/TaskService.java index bb1e7abf..ffcd01b4 100644 --- a/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/TaskService.java +++ b/platform-server-kernel/src/main/java/uno/cod/platform/server/core/service/TaskService.java @@ -2,19 +2,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import uno.cod.platform.server.core.domain.Endpoint; -import uno.cod.platform.server.core.domain.Organization; -import uno.cod.platform.server.core.domain.Runner; -import uno.cod.platform.server.core.domain.Task; +import uno.cod.platform.server.core.domain.*; import uno.cod.platform.server.core.dto.task.TaskCreateDto; import uno.cod.platform.server.core.dto.task.TaskShowDto; +import uno.cod.platform.server.core.exception.CodunoAccessDeniedException; import uno.cod.platform.server.core.exception.CodunoIllegalArgumentException; import uno.cod.platform.server.core.mapper.TaskMapper; -import uno.cod.platform.server.core.repository.EndpointRepository; -import uno.cod.platform.server.core.repository.OrganizationRepository; -import uno.cod.platform.server.core.repository.RunnerRepository; -import uno.cod.platform.server.core.repository.TaskRepository; +import uno.cod.platform.server.core.repository.*; +import javax.servlet.http.HttpSession; import javax.transaction.Transactional; import java.util.List; import java.util.UUID; @@ -26,13 +22,28 @@ public class TaskService { private final EndpointRepository endpointRepository; private final OrganizationRepository organizationRepository; private final RunnerRepository runnerRepository; + private final ChallengeRepository challengeRepository; + private final UserRepository userRepository; + private final ResultRepository resultRepository; + private final HttpSession httpSession; @Autowired - public TaskService(TaskRepository repository, EndpointRepository endpointRepository, OrganizationRepository organizationRepository, RunnerRepository runnerRepository) { + public TaskService(TaskRepository repository, + EndpointRepository endpointRepository, + OrganizationRepository organizationRepository, + RunnerRepository runnerRepository, + ChallengeRepository challengeRepository, + UserRepository userRepository, + ResultRepository resultRepository, + HttpSession httpSession) { this.repository = repository; this.endpointRepository = endpointRepository; this.organizationRepository = organizationRepository; this.runnerRepository = runnerRepository; + this.challengeRepository = challengeRepository; + this.userRepository = userRepository; + this.resultRepository = resultRepository; + this.httpSession = httpSession; } public UUID save(TaskCreateDto dto) { @@ -68,8 +79,28 @@ public UUID save(TaskCreateDto dto) { return repository.save(task).getId(); } - public TaskShowDto findById(UUID id) { - return TaskMapper.map(repository.findOneWithTemplates(id)); + public TaskShowDto findById(UUID taskId, User u) { + Task task = repository.findOneWithTemplates(taskId); + User user = userRepository.findOne(u.getId()); + Object attr = httpSession.getAttribute(ResultService.CURRENT_CHALLENGE); + if (attr == null) { + throw new CodunoAccessDeniedException("challenge.invalid"); + } + + Challenge challenge = challengeRepository.findOne((UUID)attr); + + if (canSeeTask(user, challenge, task)) { + return TaskMapper.map(task); + } + throw new CodunoAccessDeniedException("task.denied"); + } + + private boolean canSeeTask(User user, Challenge challenge, Task task) { + int requestedIndex = challenge.getChallengeTemplate().getTasks().indexOf(task); + + // User can see task if it is the first task (requestedIndex == 0) or the previous task was completed + // successfully. + return requestedIndex == 0 || resultRepository.findOneByUserAndChallenge(user.getId(), challenge.getId()).getTaskResults().get(requestedIndex - 1).isSuccessful(); } public List findAllForOrganization(UUID organizationId) { diff --git a/platform-server-kernel/src/test/java/uno/cod/platform/server/core/service/TaskServiceTest.java b/platform-server-kernel/src/test/java/uno/cod/platform/server/core/service/TaskServiceTest.java index 7fba8e91..9a55fa31 100644 --- a/platform-server-kernel/src/test/java/uno/cod/platform/server/core/service/TaskServiceTest.java +++ b/platform-server-kernel/src/test/java/uno/cod/platform/server/core/service/TaskServiceTest.java @@ -4,15 +4,17 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import uno.cod.platform.server.core.domain.Task; +import uno.cod.platform.server.core.domain.*; import uno.cod.platform.server.core.dto.task.TaskCreateDto; import uno.cod.platform.server.core.dto.task.TaskShowDto; -import uno.cod.platform.server.core.repository.EndpointRepository; -import uno.cod.platform.server.core.repository.OrganizationRepository; -import uno.cod.platform.server.core.repository.RunnerRepository; -import uno.cod.platform.server.core.repository.TaskRepository; +import uno.cod.platform.server.core.exception.CodunoAccessDeniedException; +import uno.cod.platform.server.core.repository.*; +import uno.cod.platform.server.core.service.util.ChallengeTestUtil; import uno.cod.platform.server.core.service.util.TaskTestUtil; +import uno.cod.platform.server.core.service.util.UserTestUtil; +import javax.servlet.http.HttpSession; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -23,6 +25,10 @@ public class TaskServiceTest { private EndpointRepository endpointRepository; private OrganizationRepository organizationRepository; private RunnerRepository runnerRepository; + private ChallengeRepository challengeRepository; + private UserRepository userRepository; + private ResultRepository resultRepository; + private HttpSession httpSession; @Before public void setup() { @@ -30,7 +36,11 @@ public void setup() { this.endpointRepository = Mockito.mock(EndpointRepository.class); this.organizationRepository = Mockito.mock(OrganizationRepository.class); this.runnerRepository = Mockito.mock(RunnerRepository.class); - this.service = new TaskService(repository, endpointRepository, organizationRepository, runnerRepository); + this.challengeRepository = Mockito.mock(ChallengeRepository.class); + this.userRepository = Mockito.mock(UserRepository.class); + this.resultRepository = Mockito.mock(ResultRepository.class); + this.httpSession = Mockito.mock(HttpSession.class); + this.service = new TaskService(repository, endpointRepository, organizationRepository, runnerRepository, challengeRepository, userRepository, resultRepository, httpSession); } @Test @@ -48,16 +58,57 @@ public void save() throws Exception { Assert.assertEquals(id, task.getId()); } - @Test - public void findById() throws Exception { + + @Test(expected = CodunoAccessDeniedException.class) + public void findByIdAccessDenied() throws Exception { + // Neither the session attribute is set nor the repository methods are mocked and therefore return null Task task = TaskTestUtil.getValidTask(); - Mockito.when(repository.findOneWithTemplates(task.getId())).thenReturn(task); + User user = UserTestUtil.getUser(); - TaskShowDto dto = service.findById(task.getId()); + Mockito.when(repository.findOneWithTemplates(task.getId())).thenReturn(task); + TaskShowDto dto = service.findById(task.getId(), user); assertTaskEquals(task, dto); } + @Test + public void findByIdAccessGranted() throws Exception { + Task task = TaskTestUtil.getValidTask(); + Task newTask = TaskTestUtil.getValidTask(); + User user = UserTestUtil.getUser(); + Challenge challenge = ChallengeTestUtil.getChallenge(); + + Result result = new Result(); + result.setChallenge(challenge); + result.setUser(user); + + TaskResult taskResult = new TaskResult(); + taskResult.setSuccessful(true); + + TaskResultKey taskResultKey = new TaskResultKey(); + taskResultKey.setResult(result); + taskResultKey.setTask(task); + + ArrayList taskResultList = new ArrayList<>(); + taskResultList.add(taskResult); + result.setTaskResults(taskResultList); + + ArrayList tasks = new ArrayList<>(); + tasks.add(task); + tasks.add(newTask); + challenge.getChallengeTemplate().setTasks(tasks); + + Mockito.when(httpSession.getAttribute(ResultService.CURRENT_CHALLENGE)).thenReturn(challenge.getId()); + Mockito.when(userRepository.findOne(user.getId())).thenReturn(user); + Mockito.when(repository.findOneWithTemplates(task.getId())).thenReturn(task); + Mockito.when(repository.findOneWithTemplates(newTask.getId())).thenReturn(newTask); + Mockito.when(challengeRepository.findOne(challenge.getId())).thenReturn(challenge); + Mockito.when(resultRepository.findOneByUserAndChallenge(user.getId(), challenge.getId())).thenReturn(result); + + TaskShowDto dto = service.findById(newTask.getId(), user); + assertTaskEquals(newTask, dto); + } + @Test public void findAll() throws Exception { Task task = TaskTestUtil.getValidTask(); diff --git a/platform-server-rest/src/main/java/uno/cod/platform/server/rest/controller/TaskController.java b/platform-server-rest/src/main/java/uno/cod/platform/server/rest/controller/TaskController.java index d3ed9516..c01e2d0f 100644 --- a/platform-server-rest/src/main/java/uno/cod/platform/server/rest/controller/TaskController.java +++ b/platform-server-rest/src/main/java/uno/cod/platform/server/rest/controller/TaskController.java @@ -4,7 +4,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import uno.cod.platform.server.core.domain.User; import uno.cod.platform.server.core.dto.task.TaskCreateDto; import uno.cod.platform.server.core.dto.task.TaskShowDto; import uno.cod.platform.server.core.security.AllowedForAdmin; @@ -32,8 +34,8 @@ public ResponseEntity create(@Valid @RequestBody TaskCreateDto dto) { @RequestMapping(value = RestUrls.TASKS_ID, method = RequestMethod.GET) @PreAuthorize("isAuthenticated() and @securityService.canAccessTask(principal, #id)") - public ResponseEntity findById(@PathVariable UUID id) { - return new ResponseEntity<>(taskService.findById(id), HttpStatus.OK); + public ResponseEntity findById(@PathVariable UUID id, @AuthenticationPrincipal User user) { + return new ResponseEntity<>(taskService.findById(id, user), HttpStatus.OK); } @RequestMapping(value = RestUrls.TASKS, method = RequestMethod.GET, params = {"organization"})