Skip to content

Commit

Permalink
fix: Set CSP header for attachments [DHIS2-17310](2.39) (#18400)
Browse files Browse the repository at this point in the history
* fix: Set CSP header for attachments [DHIS2-17310]

* Do not send attachment header for images

* Update dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceControllerTest.java
  • Loading branch information
enricocolasante authored Aug 22, 2024
1 parent 8c324d8 commit 8763c7a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
import org.hisp.dhis.dxf2.importsummary.ImportSummary;
import org.hisp.dhis.dxf2.webmessage.WebMessage;
import org.hisp.dhis.dxf2.webmessage.WebMessageException;
import org.hisp.dhis.external.conf.ConfigurationKey;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
import org.hisp.dhis.fieldfilter.FieldFilterParams;
import org.hisp.dhis.fieldfilter.FieldFilterService;
import org.hisp.dhis.fileresource.FileResource;
Expand Down Expand Up @@ -98,6 +100,7 @@
import org.hisp.dhis.webapi.strategy.old.tracker.imports.request.TrackerEntityInstanceRequest;
import org.hisp.dhis.webapi.utils.ContextUtils;
import org.hisp.dhis.webapi.utils.FileResourceUtils;
import org.hisp.dhis.webapi.utils.HeaderUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down Expand Up @@ -146,6 +149,8 @@ public class TrackedEntityInstanceController {

private final TrackedEntityInstanceStrategyHandler trackedEntityInstanceStrategyHandler;

private final DhisConfigurationProvider config;

// -------------------------------------------------------------------------
// READ
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -261,9 +266,7 @@ public void getAttributeImage(
FileResourceUtils.setImageFileDimensions(
fileResource, MoreObjects.firstNonNull(dimension, ImageFileDimension.ORIGINAL));

response.setContentType(fileResource.getContentType());
response.setContentLengthLong(fileResource.getContentLength());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "filename=" + fileResource.getName());
setHttpResponse(response, fileResource);

try (InputStream inputStream = fileResourceService.getFileResourceContent(fileResource)) {
BufferedImage img = ImageIO.read(inputStream);
Expand Down Expand Up @@ -538,4 +541,11 @@ private TrackedEntityInstanceParams getTrackedEntityInstanceParams(List<String>

return params;
}

private void setHttpResponse(HttpServletResponse response, FileResource fileResource) {
response.setContentType(fileResource.getContentType());
response.setContentLengthLong(fileResource.getContentLength());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "filename=" + fileResource.getName());
HeaderUtils.setSecurityHeaders(response, config.getProperty(ConfigurationKey.CSP_HEADER_VALUE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
import javax.servlet.http.HttpServletResponse;

public class HeaderUtils {
public static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff";
public static final String X_XSS_PROTECTION_VALUE = "1; mode=block";

public static void setSecurityHeaders(HttpServletResponse response, String cspHeaders) {
response.setHeader("Content-Security-Policy", cspHeaders);
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("X-Content-Type-Options", X_CONTENT_TYPE_OPTIONS_VALUE);
response.setHeader("X-XSS-Protection", X_XSS_PROTECTION_VALUE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,50 @@
*/
package org.hisp.dhis.webapi.controller.event;

import static org.hisp.dhis.webapi.utils.HeaderUtils.X_CONTENT_TYPE_OPTIONS_VALUE;
import static org.hisp.dhis.webapi.utils.HeaderUtils.X_XSS_PROTECTION_VALUE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;
import org.hisp.dhis.common.CodeGenerator;
import org.hisp.dhis.common.ValueType;
import org.hisp.dhis.dxf2.events.trackedentity.TrackedEntityInstanceService;
import org.hisp.dhis.dxf2.importsummary.ImportSummaries;
import org.hisp.dhis.external.conf.ConfigurationKey;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
import org.hisp.dhis.fileresource.FileResource;
import org.hisp.dhis.fileresource.FileResourceDomain;
import org.hisp.dhis.fileresource.FileResourceService;
import org.hisp.dhis.fileresource.FileResourceStorageStatus;
import org.hisp.dhis.schema.descriptors.TrackedEntityInstanceSchemaDescriptor;
import org.hisp.dhis.trackedentity.TrackedEntityAttribute;
import org.hisp.dhis.trackedentity.TrackedEntityInstance;
import org.hisp.dhis.trackedentity.TrackerAccessManager;
import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue;
import org.hisp.dhis.user.CurrentUserService;
import org.hisp.dhis.user.User;
import org.hisp.dhis.webapi.controller.exception.BadRequestException;
import org.hisp.dhis.webapi.strategy.old.tracker.imports.impl.TrackedEntityInstanceAsyncStrategyImpl;
import org.hisp.dhis.webapi.strategy.old.tracker.imports.impl.TrackedEntityInstanceStrategyImpl;
import org.hisp.dhis.webapi.strategy.old.tracker.imports.impl.TrackedEntityInstanceSyncStrategyImpl;
import org.hisp.dhis.webapi.utils.ContextUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
Expand All @@ -70,12 +89,16 @@ class TrackedEntityInstanceControllerTest {

@Mock private TrackedEntityInstanceSyncStrategyImpl trackedEntityInstanceSyncStrategy;

@Mock private DhisConfigurationProvider config;

@Mock private User user;

@Mock private org.hisp.dhis.trackedentity.TrackedEntityInstanceService instanceService;

@Mock private TrackerAccessManager trackerAccessManager;

@Mock private FileResourceService fileResourceService;

@Mock private TrackedEntityInstance trackedEntityInstance;

private static final String ENDPOINT = TrackedEntityInstanceSchemaDescriptor.API_ENDPOINT;
Expand All @@ -90,16 +113,55 @@ public void setUp() throws BadRequestException, IOException {
null,
null,
currentUserService,
null,
fileResourceService,
trackerAccessManager,
null,
null,
new TrackedEntityInstanceStrategyImpl(
trackedEntityInstanceSyncStrategy, trackedEntityInstanceAsyncStrategy));
trackedEntityInstanceSyncStrategy, trackedEntityInstanceAsyncStrategy),
config);

mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
when(currentUserService.getCurrentUser()).thenReturn(user);
when(user.getUid()).thenReturn("userId");
}

@Test
void shouldRetrieveImageAsAnAttachment() throws Exception {
String teUid = CodeGenerator.generateUid();
String attributeUid = CodeGenerator.generateUid();
TrackedEntityAttribute attribute = new TrackedEntityAttribute();
attribute.setUid(attributeUid);
attribute.setValueType(ValueType.IMAGE);
TrackedEntityAttributeValue attributeValue = new TrackedEntityAttributeValue();
attributeValue.setAttribute(attribute);
attributeValue.setValue("fileName");
FileResource fileResource = new FileResource();
fileResource.setDomain(FileResourceDomain.DATA_VALUE);
fileResource.setStorageStatus(FileResourceStorageStatus.STORED);
fileResource.setContentType("image/png");
fileResource.setName("dhis2.png");

File file = new ClassPathResource("images/dhis2.png").getFile();

when(instanceService.getTrackedEntityInstance(teUid)).thenReturn(trackedEntityInstance);
when(trackedEntityInstance.getTrackedEntityAttributeValues())
.thenReturn(Set.of(attributeValue));
when(fileResourceService.getFileResource("fileName")).thenReturn(fileResource);
when(fileResourceService.getFileResourceContent(fileResource))
.thenReturn(new FileInputStream(file));
when(config.getProperty(ConfigurationKey.CSP_HEADER_VALUE)).thenReturn("script-src 'none';");

mockMvc
.perform(
get(ENDPOINT + "/" + teUid + "/" + attributeUid + "/image")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isOk())
.andExpect(header().string(ContextUtils.HEADER_CONTENT_DISPOSITION, "filename=dhis2.png"))
.andExpect(header().string("Content-Security-Policy", "script-src 'none';"))
.andExpect(header().string("X-XSS-Protection", X_XSS_PROTECTION_VALUE))
.andExpect(header().string("X-Content-Type-Options", X_CONTENT_TYPE_OPTIONS_VALUE))
.andReturn();
}

@Test
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8763c7a

Please sign in to comment.