diff --git a/src/aat/java/uk/gov/hmcts/reform/em/stitching/functional/DocumentTaskAuthorizationScenarios.java b/src/aat/java/uk/gov/hmcts/reform/em/stitching/functional/DocumentTaskAuthorizationScenarios.java index a004c8c01..c57ac615c 100644 --- a/src/aat/java/uk/gov/hmcts/reform/em/stitching/functional/DocumentTaskAuthorizationScenarios.java +++ b/src/aat/java/uk/gov/hmcts/reform/em/stitching/functional/DocumentTaskAuthorizationScenarios.java @@ -52,6 +52,48 @@ void setupNonCaseworkerUser() { .header(SERVICE_AUTH_HEADER, testUtil.getS2sAuth()); } + @Test + void shouldReturnNotFoundWhenDifferentUserAttemptsToGetAnotherUsersTask() throws IOException { + BundleDTO bundle = testUtil.getTestBundle(); + DocumentTaskDTO documentTask = new DocumentTaskDTO(); + documentTask.setBundle(bundle); + + Response createTaskResponse = testUtil.authRequest() + .body(convertObjectToJsonBytes(documentTask)) + .post(DOCUMENT_TASKS_ENDPOINT); + + assertEquals(201, createTaskResponse.getStatusCode(), + "Primary user should be able to create a task."); + String taskId = createTaskResponse.getBody().jsonPath().getString("id"); + + Response getTaskResponse = nonCaseworkerRequest + .get(DOCUMENT_TASKS_ENDPOINT + "/" + taskId); + + assertEquals(404, getTaskResponse.getStatusCode(), + "A different authenticated user must not be able to retrieve another user's task (IDOR)."); + } + + @Test + void shouldReturnTaskWhenOwnerRequestsTheirOwnTask() throws IOException { + BundleDTO bundle = testUtil.getTestBundle(); + DocumentTaskDTO documentTask = new DocumentTaskDTO(); + documentTask.setBundle(bundle); + + Response createTaskResponse = testUtil.authRequest() + .body(convertObjectToJsonBytes(documentTask)) + .post(DOCUMENT_TASKS_ENDPOINT); + + assertEquals(201, createTaskResponse.getStatusCode(), + "Primary user should be able to create a task."); + String taskId = createTaskResponse.getBody().jsonPath().getString("id"); + + Response getTaskResponse = testUtil.authRequest() + .get(DOCUMENT_TASKS_ENDPOINT + "/" + taskId); + + assertEquals(200, getTaskResponse.getStatusCode(), + "The task owner must be able to retrieve their own task."); + } + @Test void shouldFailTaskWhenUserHasNoCaseworkerRole() throws IOException, InterruptedException { BundleDTO bundle = testUtil.getTestBundle(); @@ -69,7 +111,7 @@ void shouldFailTaskWhenUserHasNoCaseworkerRole() throws IOException, Interrupted String taskUrl = DOCUMENT_TASKS_ENDPOINT + "/" + createTaskResponse.getBody().jsonPath().getString("id"); - Response taskResponse = testUtil.pollUntil(taskUrl, body -> { + Response taskResponse = testUtil.pollUntil(taskUrl, nonCaseworkerRequest, body -> { String state = body.getString(TASK_STATE_FIELD); return "FAILED".equals(state) || "DONE".equals(state); }); diff --git a/src/aat/java/uk/gov/hmcts/reform/em/stitching/testutil/TestUtil.java b/src/aat/java/uk/gov/hmcts/reform/em/stitching/testutil/TestUtil.java index 1d15eb682..66b1a8242 100644 --- a/src/aat/java/uk/gov/hmcts/reform/em/stitching/testutil/TestUtil.java +++ b/src/aat/java/uk/gov/hmcts/reform/em/stitching/testutil/TestUtil.java @@ -379,15 +379,23 @@ private BundleDocumentDTO getTestBundleDocumentWithSortIndices(String documentUr public Response pollUntil(String endpoint, Predicate evaluator) throws InterruptedException, IOException { - return pollUntil(endpoint, evaluator, 30); + return pollUntil(endpoint, authRequest(), evaluator, 30); + } + + public Response pollUntil(String endpoint, + RequestSpecification requestSpec, + Predicate evaluator) + throws InterruptedException, IOException { + return pollUntil(endpoint, requestSpec, evaluator, 30); } private Response pollUntil(String endpoint, + RequestSpecification requestSpec, Predicate evaluator, int numRetries) throws InterruptedException, IOException { for (int i = 0; i < numRetries; i++) { - Response response = authRequest().get(endpoint); + Response response = requestSpec.get(endpoint); if (response.getStatusCode() == 500) { throw new IOException("HTTP 500 from service"); diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/em/stitching/rest/DocumentTaskResourceIntTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/em/stitching/rest/DocumentTaskResourceIntTest.java index 2d5b65bf4..edde7f951 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/em/stitching/rest/DocumentTaskResourceIntTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/em/stitching/rest/DocumentTaskResourceIntTest.java @@ -18,6 +18,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; import uk.gov.hmcts.reform.em.stitching.Application; +import uk.gov.hmcts.reform.em.stitching.config.security.SecurityUtils; import uk.gov.hmcts.reform.em.stitching.domain.Bundle; import uk.gov.hmcts.reform.em.stitching.domain.BundleTest; import uk.gov.hmcts.reform.em.stitching.domain.DocumentTask; @@ -32,6 +33,7 @@ import java.io.IOException; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -73,6 +75,9 @@ class DocumentTaskResourceIntTest { @Autowired private OkHttpClient okHttpClient; + @MockitoBean + private SecurityUtils securityUtils; + @MockitoBean private AuthTokenGenerator authTokenGenerator; @@ -112,6 +117,7 @@ void initTest() { defaultTestDocumentTask = createEntity(); MockInterceptor mockInterceptor = (MockInterceptor)okHttpClient.interceptors().get(0); mockInterceptor.reset(); + BDDMockito.given(securityUtils.getCurrentUserLogin()).willReturn(Optional.of("test-user")); } @Test @@ -193,6 +199,7 @@ void createDocumentTaskWithExistingId() throws Exception { @Test void getDocumentTask() throws Exception { // Initialize the database + defaultTestDocumentTask.setCreatedBy("test-user"); documentTaskRepository.saveAndFlush(defaultTestDocumentTask); // Get the documentTask diff --git a/src/main/java/uk/gov/hmcts/reform/em/stitching/repository/DocumentTaskRepository.java b/src/main/java/uk/gov/hmcts/reform/em/stitching/repository/DocumentTaskRepository.java index ad7895945..7647900be 100644 --- a/src/main/java/uk/gov/hmcts/reform/em/stitching/repository/DocumentTaskRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/em/stitching/repository/DocumentTaskRepository.java @@ -8,10 +8,14 @@ import java.time.Instant; import java.util.List; +import java.util.Optional; @Repository public interface DocumentTaskRepository extends JpaRepository { + Optional findByIdAndCreatedBy(Long id, String createdBy); + + @Query(value = "SELECT m.id FROM versioned_document_task m WHERE m.created_date <= :createdDate limit :numberOfRecords", nativeQuery = true) diff --git a/src/main/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImpl.java b/src/main/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImpl.java index 7f34681a2..263e179c1 100644 --- a/src/main/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImpl.java @@ -2,8 +2,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import uk.gov.hmcts.reform.em.stitching.config.security.SecurityUtils; import uk.gov.hmcts.reform.em.stitching.domain.DocumentTask; import uk.gov.hmcts.reform.em.stitching.info.BuildInfo; import uk.gov.hmcts.reform.em.stitching.repository.DocumentTaskRepository; @@ -19,17 +21,22 @@ @Service public class DocumentTaskServiceImpl implements DocumentTaskService { + public static final String USER_NOT_FOUND = "User not found."; + private final Logger log = LoggerFactory.getLogger(DocumentTaskServiceImpl.class); private final DocumentTaskRepository documentTaskRepository; private final DocumentTaskMapper documentTaskMapper; private final BuildInfo buildInfo; + private final SecurityUtils securityUtils; public DocumentTaskServiceImpl(DocumentTaskRepository documentTaskRepository, DocumentTaskMapper documentTaskMapper, - BuildInfo buildInfo) { + BuildInfo buildInfo, + SecurityUtils securityUtils) { this.documentTaskRepository = documentTaskRepository; this.documentTaskMapper = documentTaskMapper; this.buildInfo = buildInfo; + this.securityUtils = securityUtils; } /** @@ -61,7 +68,9 @@ public DocumentTaskDTO save(DocumentTaskDTO documentTaskDto) { @Transactional(readOnly = true) public Optional findOne(Long id) { log.debug("Request to get DocumentTask : {}", id); - return documentTaskRepository.findById(id) + String currentUser = securityUtils.getCurrentUserLogin() + .orElseThrow(() -> new UsernameNotFoundException(USER_NOT_FOUND)); + return documentTaskRepository.findByIdAndCreatedBy(id, currentUser) .map(documentTaskMapper::toDto); } diff --git a/src/test/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImplTest.java b/src/test/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImplTest.java new file mode 100644 index 000000000..b3de3889e --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/em/stitching/service/impl/DocumentTaskServiceImplTest.java @@ -0,0 +1,91 @@ +package uk.gov.hmcts.reform.em.stitching.service.impl; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import uk.gov.hmcts.reform.em.stitching.config.security.SecurityUtils; +import uk.gov.hmcts.reform.em.stitching.domain.DocumentTask; +import uk.gov.hmcts.reform.em.stitching.info.BuildInfo; +import uk.gov.hmcts.reform.em.stitching.repository.DocumentTaskRepository; +import uk.gov.hmcts.reform.em.stitching.service.dto.DocumentTaskDTO; +import uk.gov.hmcts.reform.em.stitching.service.mapper.DocumentTaskMapper; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DocumentTaskServiceImplTest { + + @Mock + private DocumentTaskRepository documentTaskRepository; + + @Mock + private DocumentTaskMapper documentTaskMapper; + + @Mock + private BuildInfo buildInfo; + + @Mock + private SecurityUtils securityUtils; + + @InjectMocks + private DocumentTaskServiceImpl documentTaskService; + + private static final Long TASK_ID = 1L; + private static final String OWNER = "user-abc"; + private static final String OTHER_USER = "user-xyz"; + + @Test + void findOneReturnsTaskWhenOwnedByCurrentUser() { + DocumentTask task = new DocumentTask(); + DocumentTaskDTO taskDTO = new DocumentTaskDTO(); + when(securityUtils.getCurrentUserLogin()).thenReturn(Optional.of(OWNER)); + when(documentTaskRepository.findByIdAndCreatedBy(TASK_ID, OWNER)).thenReturn(Optional.of(task)); + when(documentTaskMapper.toDto(task)).thenReturn(taskDTO); + + Optional result = documentTaskService.findOne(TASK_ID); + + assertTrue(result.isPresent()); + assertEquals(taskDTO, result.get()); + verify(documentTaskRepository).findByIdAndCreatedBy(TASK_ID, OWNER); + } + + @Test + void findOneReturnsEmptyWhenTaskBelongsToDifferentUser() { + when(securityUtils.getCurrentUserLogin()).thenReturn(Optional.of(OTHER_USER)); + when(documentTaskRepository.findByIdAndCreatedBy(TASK_ID, OTHER_USER)).thenReturn(Optional.empty()); + + Optional result = documentTaskService.findOne(TASK_ID); + + assertTrue(result.isEmpty()); + verify(documentTaskRepository).findByIdAndCreatedBy(TASK_ID, OTHER_USER); + } + + @Test + void findOneThrowsWhenNoAuthenticatedUser() { + when(securityUtils.getCurrentUserLogin()).thenReturn(Optional.empty()); + + assertThrows(UsernameNotFoundException.class, () -> documentTaskService.findOne(TASK_ID)); + + verify(documentTaskRepository, never()).findByIdAndCreatedBy(TASK_ID, null); + } + + @Test + void findOneReturnsEmptyWhenTaskDoesNotExist() { + when(securityUtils.getCurrentUserLogin()).thenReturn(Optional.of(OWNER)); + when(documentTaskRepository.findByIdAndCreatedBy(TASK_ID, OWNER)).thenReturn(Optional.empty()); + + Optional result = documentTaskService.findOne(TASK_ID); + + assertTrue(result.isEmpty()); + } +}