diff --git a/src/main/java/org/tailormap/api/controller/LayerExportController.java b/src/main/java/org/tailormap/api/controller/LayerExportController.java index 57c123646..29f259468 100644 --- a/src/main/java/org/tailormap/api/controller/LayerExportController.java +++ b/src/main/java/org/tailormap/api/controller/LayerExportController.java @@ -27,6 +27,7 @@ import org.geotools.data.wfs.WFSDataStoreFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -63,6 +64,9 @@ public class LayerExportController { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @Value("#{'${tailormap-api.export.allowed-outputformats}'.split(',')}") + private List allowedOutputFormats; + private final FeatureSourceRepository featureSourceRepository; public LayerExportController(FeatureSourceRepository featureSourceRepository) { @@ -128,6 +132,12 @@ public ResponseEntity download( @RequestParam(required = false) String crs, HttpServletRequest request) { + // Validate outputFormat + if (!allowedOutputFormats.contains(outputFormat)) { + logger.warn("Invalid output format requested: {}", outputFormat); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid output format"); + } + TMFeatureType tmft = service.findFeatureTypeForLayer(layer, featureSourceRepository); AppLayerSettings appLayerSettings = application.getAppLayerSettings(appTreeLayerNode); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index da1d7e6c2..af74bd821 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,6 +21,9 @@ tailormap-api.features.wfs_count_exact=false # maximum number of items to return in a single (WFS/JDBC) feature info request tailormap-api.feature.info.maxitems=30 +# Should match the list in tailormap-viewer class AttributeListExportService +tailormap-api.export.allowed-outputformats=csv,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,excel2007,application/vnd.shp,application/x-zipped-shp,SHAPE-ZIP,application/geopackage+sqlite3,application/x-gpkg,geopackage,geopkg,gpkg,application/geo+json,application/geojson,application/json,json,DXF-ZIP + # whether the API should use GeoTools "Unique Collection" (use DISTINCT in SQL statements) or just # retrieve all values when calculating the unique values for a property. # There might be a performance difference between the two, depending on the data diff --git a/src/test/java/org/tailormap/api/controller/LayerExportControllerIntegrationTest.java b/src/test/java/org/tailormap/api/controller/LayerExportControllerIntegrationTest.java index 4493f49ad..f1e807088 100644 --- a/src/test/java/org/tailormap/api/controller/LayerExportControllerIntegrationTest.java +++ b/src/test/java/org/tailormap/api/controller/LayerExportControllerIntegrationTest.java @@ -109,7 +109,7 @@ void shouldExportGeoJSON() throws Exception { get(url) .with(setServletPath(url)) .accept(MediaType.APPLICATION_JSON) - .param("outputFormat", "application/json") + .param("outputFormat", MediaType.APPLICATION_JSON_VALUE) .param("attributes", "geom,naam,code")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) @@ -159,7 +159,7 @@ void shouldExportGeoJSONWithFilterAndSort() throws Exception { get(url) .accept(MediaType.APPLICATION_JSON) .with(setServletPath(url)) - .param("outputFormat", "application/json") + .param("outputFormat", MediaType.APPLICATION_JSON_VALUE) .param("filter", "(BRONHOUDER IN ('G1904','L0002','L0004'))") .param("sortBy", "CLASS") .param("sortOrder", "asc")) @@ -176,7 +176,7 @@ void shouldExportGeoJSONWithFilterAndSort() throws Exception { get(url) .accept(MediaType.APPLICATION_JSON) .with(setServletPath(url)) - .param("outputFormat", "application/json") + .param("outputFormat", MediaType.APPLICATION_JSON_VALUE) .param("filter", "(BRONHOUDER IN ('G1904','L0002','L0004'))") .param("sortBy", "CLASS") .param("sortOrder", "desc")) @@ -197,7 +197,7 @@ void shouldNotExportHiddenAttributesInGeoJSONWhenRequested() throws Exception { get(url) .accept(MediaType.APPLICATION_JSON) .with(setServletPath(url)) - .param("outputFormat", "application/json") + .param("outputFormat", MediaType.APPLICATION_JSON_VALUE) // terminationdate,geom_kruinlijn are hidden attributes .param( "attributes", "identificatie,bronhouder,class,terminationdate,geom_kruinlijn")) @@ -216,7 +216,7 @@ void shouldNotExportHiddenAttributesInGeoJSON() throws Exception { get(url) .accept(MediaType.APPLICATION_JSON) .with(setServletPath(url)) - .param("outputFormat", "application/json") + .param("outputFormat", MediaType.APPLICATION_JSON_VALUE) .param("filter", "(bronhouder ILIKE 'L0001')")) .andDo(MockMvcResultHandlers.print()) .andExpect(status().isOk()) @@ -237,7 +237,19 @@ void test_wms_secured_proxy_not_in_public_app() throws Exception { get(testUrl) .accept(MediaType.APPLICATION_JSON) .with(setServletPath(testUrl)) - .param("outputFormat", "application/json")) + .param("outputFormat", MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isForbidden()); } + + @Test + void testInvalidOutputFormatNotAccepted() throws Exception { + final String testUrl = apiBasePath + layerBegroeidTerreindeelPostgis + "/export/download"; + mockMvc + .perform( + get(testUrl) + .accept(MediaType.APPLICATION_JSON) + .with(setServletPath(testUrl)) + .param("outputFormat", "Invalid value!")) + .andExpect(status().isBadRequest()); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 666db8573..1a917e659 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -2,6 +2,7 @@ tailormap-api.base-path=/api tailormap-api.admin.base-path=/api/admin management.endpoints.web.base-path=/api/actuator tailormap-api.new-admin-username=tm-admin +tailormap-api.export.allowed-outputformats=application/geopackage+sqlite3,application/json tailormap-api.timeout=5000 tailormap-api.management.hashed-password=#{null} spring.profiles.active=test