From 73c3235c2442e285274ed7857098eaebdd0297d6 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 4 Jul 2023 10:39:31 +0100 Subject: [PATCH 01/34] Stash: getVersionFiles API extension with pagination and order criteria WIP --- .../dataverse/DatasetVersionServiceBean.java | 76 ++++++++++++++++++- .../harvard/iq/dataverse/api/Datasets.java | 12 ++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 9f272ec6877..5100f9d26eb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -49,7 +49,21 @@ public class DatasetVersionServiceBean implements java.io.Serializable { private static final Logger logger = Logger.getLogger(DatasetVersionServiceBean.class.getCanonicalName()); private static final SimpleDateFormat logFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss"); - + + private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_LABEL = "SELECT fm FROM FileMetadata fm WHERE fm.datasetVersion.id=:datasetVersionId ORDER BY fm.label"; + private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_DATE = "SELECT fm FROM FileMetadata fm, DvObject dvo" + + " WHERE fm.datasetVersion.id = :datasetVersionId" + + " AND fm.dataFile.id = dvo.id" + + " ORDER BY CASE WHEN dvo.publicationDate IS NOT NULL THEN dvo.publicationDate ELSE dvo.createDate END"; + private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_SIZE = "SELECT fm FROM FileMetadata fm, DataFile df" + + " WHERE fm.datasetVersion.id = :datasetVersionId" + + " AND fm.dataFile.id = df.id" + + " ORDER BY df.filesize"; + private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_TYPE = "SELECT fm FROM FileMetadata fm, DataFile df" + + " WHERE fm.datasetVersion.id = :datasetVersionId" + + " AND fm.dataFile.id = df.id" + + " ORDER BY df.contentType"; + @EJB DatasetServiceBean datasetService; @@ -150,7 +164,19 @@ public DatasetVersion getDatasetVersion(){ return this.datasetVersionForResponse; } } // end RetrieveDatasetVersionResponse - + + /** + * Different criteria to sort the results of FileMetadata queries used in {@link DatasetVersionServiceBean#getFileMetadatas(DatasetVersion, Integer, Integer, FileMetadatasOrderCriteria)} + */ + public enum FileMetadatasOrderCriteria { + NameAZ, + NameZA, + Newest, + Oldest, + Size, + Type + } + public DatasetVersion find(Object pk) { return em.find(DatasetVersion.class, pk); } @@ -1210,4 +1236,50 @@ public List getUnarchivedDatasetVersions(){ return null; } } // end getUnarchivedDatasetVersions + + /** + * Returns a FileMetadata list of files in the specified DatasetVersion + * + * @param datasetVersion the DatasetVersion to access + * @param limit for pagination, can be null + * @param offset for pagination, can be null + * @param orderCriteria a FileMetadatasOrderCriteria to order the results + * @return a FileMetadata list of the specified DatasetVersion + */ + public List getFileMetadatas(DatasetVersion datasetVersion, Integer limit, Integer offset, FileMetadatasOrderCriteria orderCriteria) { + Query query = em.createQuery(getQueryStringFromFileMetadatasOrderCriteria(orderCriteria)) + .setParameter("datasetVersionId", datasetVersion.getId()); + if (limit != null) { + query.setMaxResults(limit); + } + if (offset != null) { + query.setFirstResult(offset); + } + return query.getResultList(); + } + + private String getQueryStringFromFileMetadatasOrderCriteria(FileMetadatasOrderCriteria orderCriteria) { + String queryString; + switch (orderCriteria) { + case NameZA: + queryString = QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_LABEL + " DESC"; + break; + case Newest: + queryString = QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_DATE + " DESC"; + break; + case Oldest: + queryString = QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_DATE; + break; + case Size: + queryString = QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_SIZE; + break; + case Type: + queryString = QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_TYPE; + break; + default: + queryString = QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_LABEL; + break; + } + return queryString; + } } // end class diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 8c1390b597e..5aef4302631 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -493,9 +493,15 @@ public Response getVersion(@Context ContainerRequestContext crc, @PathParam("id" @GET @AuthRequired @Path("{id}/versions/{versionId}/files") - public Response getVersionFiles(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers) { - return response( req -> ok( jsonFileMetadatas( - getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers).getFileMetadatas())), getRequestUser(crc)); + public Response getVersionFiles(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, @PathParam("versionId") String versionId, @QueryParam("limit") Integer limit, @QueryParam("offset") Integer offset, @QueryParam("orderCriteria") String orderCriteria, @Context UriInfo uriInfo, @Context HttpHeaders headers) { + return response( req -> { + DatasetVersion datasetVersion = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers); + try { + return ok(jsonFileMetadatas(datasetversionService.getFileMetadatas(datasetVersion, limit, offset, orderCriteria != null ? DatasetVersionServiceBean.FileMetadatasOrderCriteria.valueOf(orderCriteria) : DatasetVersionServiceBean.FileMetadatasOrderCriteria.NameAZ))); + } catch (IllegalArgumentException e) { + return error(Response.Status.BAD_REQUEST, "Invalid order criteria: " + orderCriteria); + } + }, getRequestUser(crc)); } @GET From dc31788c09eb168565ea84c04e4cf9ed9ccd1387 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 4 Jul 2023 11:43:58 +0100 Subject: [PATCH 02/34] Stash: IT tests for getVersionFiles API endpoint WIP --- .../dataverse/DatasetVersionServiceBean.java | 2 +- .../harvard/iq/dataverse/api/DatasetsIT.java | 32 +++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 31 +++++++++++++++--- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 5100f9d26eb..45edfb01777 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -166,7 +166,7 @@ public DatasetVersion getDatasetVersion(){ } // end RetrieveDatasetVersionResponse /** - * Different criteria to sort the results of FileMetadata queries used in {@link DatasetVersionServiceBean#getFileMetadatas(DatasetVersion, Integer, Integer, FileMetadatasOrderCriteria)} + * Different criteria to sort the results of FileMetadata queries used in {@link DatasetVersionServiceBean#getFileMetadatas} */ public enum FileMetadatasOrderCriteria { NameAZ, diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 687ab453d24..973818776f3 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -9,6 +9,7 @@ import java.util.logging.Logger; +import edu.harvard.iq.dataverse.DatasetVersionServiceBean; import org.junit.BeforeClass; import org.junit.Test; import org.skyscreamer.jsonassert.JSONAssert; @@ -3202,4 +3203,35 @@ public void getDatasetVersionCitation() { // We check that the returned message contains information expected for the citation string .body("data.message", containsString("DRAFT VERSION")); } + + @Test + public void getVersionFiles() throws IOException { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String datasetPersistentId = JsonPath.from(createDatasetResponse.body().asString()).getString("data.persistentId"); + Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + + String testFileName1 = "test_1.txt"; + String testFileName2 = "test_2.txt"; + String testFileName3 = "test_3.txt"; + String testFileName4 = "test_4.txt"; + + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName1, new byte[50], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName2, new byte[100], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName3, new byte[200], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName4, new byte[300], apiToken); + + Response getVersionFilesResponseOrderNameAZ = UtilIT.getVersionFiles(datasetId, ":latest", 2, 0, DatasetVersionServiceBean.FileMetadatasOrderCriteria.NameAZ.toString(), apiToken); + getVersionFilesResponseOrderNameAZ.prettyPrint(); + + // TODO + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 6e24d0a0ecb..e0f648e48aa 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4,6 +4,8 @@ import com.jayway.restassured.http.ContentType; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; + +import java.io.*; import java.util.UUID; import java.util.logging.Logger; import javax.json.Json; @@ -12,8 +14,6 @@ import javax.json.JsonObject; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; -import java.io.File; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -30,7 +30,6 @@ import com.mashape.unirest.http.Unirest; import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.GetRequest; -import java.io.InputStream; import edu.harvard.iq.dataverse.util.FileUtil; import java.util.Base64; import org.apache.commons.io.IOUtils; @@ -49,8 +48,11 @@ import edu.harvard.iq.dataverse.DatasetFieldType; import edu.harvard.iq.dataverse.DatasetFieldValue; import edu.harvard.iq.dataverse.util.StringUtil; -import java.io.StringReader; + import java.util.Collections; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -3240,4 +3242,25 @@ static Response getDatasetVersionCitation(Integer datasetId, String version, Str .get("/api/datasets/" + datasetId + "/versions/" + version + "/citation"); return response; } + + static Response getVersionFiles(Integer datasetId, String version, int limit, int offset, String orderCriteria, String apiToken) { + Response response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .contentType("application/json") + .get("/api/datasets/" + datasetId + "/versions/" + version + "/files?limit=" + limit + "&offset=" + offset + "&orderCriteria=" + orderCriteria); + return response; + } + + static Response createAndUploadTestFile(String persistentId, String testFileName, byte[] testFileContentInBytes, String apiToken) throws IOException { + Path pathToTempDir = Paths.get(Files.createTempDirectory(null).toString()); + String pathToTestFile = pathToTempDir + File.separator + testFileName; + File testFile = new File(pathToTestFile); + FileOutputStream fileOutputStream = new FileOutputStream(testFile); + + fileOutputStream.write(testFileContentInBytes); + fileOutputStream.flush(); + fileOutputStream.close(); + + return uploadZipFileViaSword(persistentId, pathToTestFile, apiToken); + } } From fff211910e4a060f873e8949e3ea2d9653ea46d5 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 4 Jul 2023 21:44:45 +0100 Subject: [PATCH 03/34] Added: IT tests for getVersionFiles API endpoint --- .../harvard/iq/dataverse/api/DatasetsIT.java | 102 ++++++++++++++++-- .../edu/harvard/iq/dataverse/api/UtilIT.java | 18 +++- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 973818776f3..e994536f03a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -94,7 +94,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.junit.Assert.assertFalse; public class DatasetsIT { @@ -3223,15 +3222,104 @@ public void getVersionFiles() throws IOException { String testFileName2 = "test_2.txt"; String testFileName3 = "test_3.txt"; String testFileName4 = "test_4.txt"; + String testFileName5 = "test_5.png"; UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName1, new byte[50], apiToken); - UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName2, new byte[100], apiToken); - UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName3, new byte[200], apiToken); - UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName4, new byte[300], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName2, new byte[200], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName3, new byte[100], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName5, new byte[300], apiToken); + UtilIT.createAndUploadTestFile(datasetPersistentId, testFileName4, new byte[400], apiToken); - Response getVersionFilesResponseOrderNameAZ = UtilIT.getVersionFiles(datasetId, ":latest", 2, 0, DatasetVersionServiceBean.FileMetadatasOrderCriteria.NameAZ.toString(), apiToken); - getVersionFilesResponseOrderNameAZ.prettyPrint(); + String testDatasetVersion = ":latest"; - // TODO + // Test pagination and NameAZ order criteria (the default criteria) + int testPageSize = 2; + + // Test page 1 + Response getVersionFilesResponsePaginated = UtilIT.getVersionFiles(datasetId, testDatasetVersion, testPageSize, null, null, apiToken); + + int fileMetadatasCount = getVersionFilesResponsePaginated.jsonPath().getList("data").size(); + assertEquals(testPageSize, fileMetadatasCount); + + getVersionFilesResponsePaginated.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName1)) + .body("data[1].label", equalTo(testFileName2)); + + // Test page 2 + getVersionFilesResponsePaginated = UtilIT.getVersionFiles(datasetId, testDatasetVersion, testPageSize, testPageSize, null, apiToken); + + fileMetadatasCount = getVersionFilesResponsePaginated.jsonPath().getList("data").size(); + assertEquals(testPageSize, fileMetadatasCount); + + getVersionFilesResponsePaginated.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName3)) + .body("data[1].label", equalTo(testFileName4)); + + // Test page 3 (last) + getVersionFilesResponsePaginated = UtilIT.getVersionFiles(datasetId, testDatasetVersion, testPageSize, testPageSize * 2, null, apiToken); + + fileMetadatasCount = getVersionFilesResponsePaginated.jsonPath().getList("data").size(); + assertEquals(1, fileMetadatasCount); + + getVersionFilesResponsePaginated.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName5)); + + // Test NameZA order criteria + Response getVersionFilesResponseNameZACriteria = UtilIT.getVersionFiles(datasetId, testDatasetVersion, null, null, DatasetVersionServiceBean.FileMetadatasOrderCriteria.NameZA.toString(), apiToken); + + getVersionFilesResponseNameZACriteria.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName5)) + .body("data[1].label", equalTo(testFileName4)) + .body("data[2].label", equalTo(testFileName3)) + .body("data[3].label", equalTo(testFileName2)) + .body("data[4].label", equalTo(testFileName1)); + + // Test Newest order criteria + Response getVersionFilesResponseNewestCriteria = UtilIT.getVersionFiles(datasetId, testDatasetVersion, null, null, DatasetVersionServiceBean.FileMetadatasOrderCriteria.Newest.toString(), apiToken); + + getVersionFilesResponseNewestCriteria.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName4)) + .body("data[1].label", equalTo(testFileName5)) + .body("data[2].label", equalTo(testFileName3)) + .body("data[3].label", equalTo(testFileName2)) + .body("data[4].label", equalTo(testFileName1)); + + // Test Oldest order criteria + Response getVersionFilesResponseOldestCriteria = UtilIT.getVersionFiles(datasetId, testDatasetVersion, null, null, DatasetVersionServiceBean.FileMetadatasOrderCriteria.Oldest.toString(), apiToken); + + getVersionFilesResponseOldestCriteria.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName1)) + .body("data[1].label", equalTo(testFileName2)) + .body("data[2].label", equalTo(testFileName3)) + .body("data[3].label", equalTo(testFileName5)) + .body("data[4].label", equalTo(testFileName4)); + + // Test Size order criteria + Response getVersionFilesResponseSizeCriteria = UtilIT.getVersionFiles(datasetId, testDatasetVersion, null, null, DatasetVersionServiceBean.FileMetadatasOrderCriteria.Size.toString(), apiToken); + + getVersionFilesResponseSizeCriteria.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName1)) + .body("data[1].label", equalTo(testFileName3)) + .body("data[2].label", equalTo(testFileName2)) + .body("data[3].label", equalTo(testFileName5)) + .body("data[4].label", equalTo(testFileName4)); + + // Test Type order criteria + Response getVersionFilesResponseTypeCriteria = UtilIT.getVersionFiles(datasetId, testDatasetVersion, null, null, DatasetVersionServiceBean.FileMetadatasOrderCriteria.Type.toString(), apiToken); + + getVersionFilesResponseTypeCriteria.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].label", equalTo(testFileName5)) + .body("data[1].label", equalTo(testFileName1)) + .body("data[2].label", equalTo(testFileName2)) + .body("data[3].label", equalTo(testFileName3)) + .body("data[4].label", equalTo(testFileName4)); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index e0f648e48aa..37e287ab19c 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3243,12 +3243,20 @@ static Response getDatasetVersionCitation(Integer datasetId, String version, Str return response; } - static Response getVersionFiles(Integer datasetId, String version, int limit, int offset, String orderCriteria, String apiToken) { - Response response = given() + static Response getVersionFiles(Integer datasetId, String version, Integer limit, Integer offset, String orderCriteria, String apiToken) { + RequestSpecification requestSpecification = given() .header(API_TOKEN_HTTP_HEADER, apiToken) - .contentType("application/json") - .get("/api/datasets/" + datasetId + "/versions/" + version + "/files?limit=" + limit + "&offset=" + offset + "&orderCriteria=" + orderCriteria); - return response; + .contentType("application/json"); + if (limit != null) { + requestSpecification = requestSpecification.queryParam("limit", limit); + } + if (offset != null) { + requestSpecification = requestSpecification.queryParam("offset", offset); + } + if (orderCriteria != null) { + requestSpecification = requestSpecification.queryParam("orderCriteria", orderCriteria); + } + return requestSpecification.get("/api/datasets/" + datasetId + "/versions/" + version + "/files"); } static Response createAndUploadTestFile(String persistentId, String testFileName, byte[] testFileContentInBytes, String apiToken) throws IOException { From e8951a48b5e252a00f5f40a6bda14a8642d89dbe Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 5 Jul 2023 09:58:38 +0100 Subject: [PATCH 04/34] Removed: unused imports --- src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 37e287ab19c..591ab1c4222 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -50,8 +50,6 @@ import edu.harvard.iq.dataverse.util.StringUtil; import java.util.Collections; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; From 6264fa43a34e3946e2933716ad6e375350022186 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 5 Jul 2023 19:12:45 +0100 Subject: [PATCH 05/34] Added: publication date field to data file payload --- .../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 601d1c34e17..ed06a2c360b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -687,6 +687,7 @@ public static JsonObjectBuilder json(DataFile df, FileMetadata fileMetadata) { .add("fileMetadataId", fileMetadata.getId()) .add("tabularTags", getTabularFileTags(df)) .add("creationDate", df.getCreateDateFormattedYYYYMMDD()) + .add("publicationDate", df.getPublicationDateFormattedYYYYMMDD()) .add("dataTables", df.getDataTables().isEmpty() ? null : JsonPrinter.jsonDT(df.getDataTables())) .add("varGroups", fileMetadata.getVarGroups().isEmpty() ? JsonPrinter.jsonVarGroup(fileMetadata.getVarGroups()) From 639cff8287f3fcfc5179d2b75a888efe78f07216 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 6 Jul 2023 08:01:52 +0100 Subject: [PATCH 06/34] Added: getCountGuestbookResponsesByDataFileId API endpoint --- .../edu/harvard/iq/dataverse/api/Files.java | 15 +++++++- .../edu/harvard/iq/dataverse/api/FilesIT.java | 36 +++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 ++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index f6eda085c95..467341f4077 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -12,6 +12,7 @@ import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.EjbDataverseEngine; import edu.harvard.iq.dataverse.FileMetadata; +import edu.harvard.iq.dataverse.GuestbookResponseServiceBean; import edu.harvard.iq.dataverse.TermsOfUseAndAccessValidator; import edu.harvard.iq.dataverse.UserNotificationServiceBean; import edu.harvard.iq.dataverse.api.auth.AuthRequired; @@ -102,7 +103,9 @@ public class Files extends AbstractApiBean { SettingsServiceBean settingsService; @Inject MakeDataCountLoggingServiceBean mdcLogService; - + @Inject + GuestbookResponseServiceBean guestbookResponseService; + private static final Logger logger = Logger.getLogger(Files.class.getName()); @@ -818,4 +821,14 @@ public Response getExternalToolFMParams(@Context ContainerRequestContext crc, @P public Response getFixityAlgorithm() { return ok(systemConfig.getFileFixityChecksumAlgorithm().toString()); } + + @GET + @Path("{id}/guestbookResponses/count") + public Response getCountGuestbookResponsesByDataFileId(@PathParam("id") String dataFileId) { + try { + return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(Long.parseLong(dataFileId)).toString()); + } catch (NumberFormatException nfe) { + return badRequest("File identifier has to be numeric."); + } + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index ed4d255ab74..49b41d1e0e0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2018,4 +2018,40 @@ public void testDeleteFile() { .body("data.files[0]", equalTo(null)) .statusCode(OK.getStatusCode()); } + + @Test + public void testGetCountGuestbookResponsesByDataFileId() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + + // Upload test file + String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; + Response uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), pathToTestFile, Json.createObjectBuilder().build(), apiToken); + uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Publish collection and dataset + UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().assertThat().statusCode(OK.getStatusCode()); + UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode()); + + // Download test file + Integer testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + + Response downloadResponse = UtilIT.downloadFile(testFileId, apiToken); + downloadResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Get count guestbook responses and assert it is 1 + Response getGuestbookResponsesByDataFileIdResponse = UtilIT.getCountGuestbookResponsesByDataFileId(testFileId, apiToken); + getGuestbookResponsesByDataFileIdResponse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.message", equalTo("1")); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 591ab1c4222..6c2543bcf82 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3269,4 +3269,10 @@ static Response createAndUploadTestFile(String persistentId, String testFileName return uploadZipFileViaSword(persistentId, pathToTestFile, apiToken); } + + static Response getCountGuestbookResponsesByDataFileId(Integer dataFileId, String apiToken) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/files/" + dataFileId + "/guestbookResponses/count"); + } } From 886a5082b2af8f9435b4bd9d0d1ac0a6e532c0a1 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 6 Jul 2023 12:27:40 +0100 Subject: [PATCH 07/34] Added: canDownloadFile method to FileDownloadServiceBean --- .../iq/dataverse/FileDownloadServiceBean.java | 46 +++- .../FileDownloadServiceBeanTest.java | 246 ++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index a90489be29a..5c490b18ecc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -572,5 +572,49 @@ public String getDirectStorageLocatrion(String storageLocation) { return null; } - + + /** + * + * Checks if a user can download a file based on the file metadata and the permissions of the user + * + * This method is based on {@link edu.harvard.iq.dataverse.FileDownloadHelper#canDownloadFile(FileMetadata), + * and has been adapted to make it callable from the API instead of a view + * + * @param user requesting the download + * @param fileMetadata of the particular file to download + * @return boolean + */ + public boolean canDownloadFile(User user, FileMetadata fileMetadata){ + if (fileMetadata == null){ + return false; + } + if ((fileMetadata.getId() == null) || (fileMetadata.getDataFile().getId() == null)){ + return false; + } + if (user == null) { + return false; + } + if (user instanceof PrivateUrlUser) { + // Always allow download for PrivateUrlUser + return true; + } + + if (fileMetadata.getDatasetVersion().isDeaccessioned()) { + return this.permissionService.userOn(user, fileMetadata.getDatasetVersion().getDataset()).has(Permission.EditDataset); + } + + // Note that `isRestricted` at the FileMetadata level is for expressing intent by version. Enforcement is done with `isRestricted` at the DataFile level. + boolean isRestrictedFile = fileMetadata.isRestricted() || fileMetadata.getDataFile().isRestricted(); + if (!isRestrictedFile && !FileUtil.isActivelyEmbargoed(fileMetadata)){ + return true; + } + + // See if the DataverseRequest, which contains IP Groups, has permission to download the file. + if (permissionService.requestOn(dvRequestService.getDataverseRequest(), fileMetadata.getDataFile()).has(Permission.DownloadFile)) { + logger.fine("The DataverseRequest (User plus IP address) has access to download the file."); + return true; + } + + return false; + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java b/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java new file mode 100644 index 00000000000..d754dd357a9 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java @@ -0,0 +1,246 @@ +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.User; +import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; + +import static edu.harvard.iq.dataverse.mocks.MocksFactory.makeAuthenticatedUser; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +@ExtendWith(MockitoExtension.class) +public class FileDownloadServiceBeanTest { + + @Mock + private PermissionServiceBean permissionServiceBeanStub; + @Mock + private DataverseRequestServiceBean dataverseRequestServiceBeanMock; + @Mock + private MakeDataCountLoggingServiceBean makeDataCountLoggingServiceBeanMock; + + private FileDownloadServiceBean sut; + + private User testUser; + + @BeforeEach + public void setUp() { + sut = new FileDownloadServiceBean(); + sut.permissionService = permissionServiceBeanStub; + sut.dvRequestService = dataverseRequestServiceBeanMock; + sut.mdcLogService = makeDataCountLoggingServiceBeanMock; + testUser = makeAuthenticatedUser("Test", "Test"); + } + + @Test + public void testCanDownloadFile_withoutUser() { + assertFalse(sut.canDownloadFile(null, new FileMetadata())); + } + + @Test + public void testCanDownloadFile_withoutFileMetadata() { + assertFalse(sut.canDownloadFile(testUser, null)); + } + + @Test + void testCanDownloadFile_withNullMetadataId() { + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(null); + + assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_withNullDataFileId() { + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + DataFile testDataFile = new DataFile(); + testDataFile.setId(null); + testFileMetadata.setDataFile(testDataFile); + + assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forDeaccessionedFile(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setDataset(new Dataset()); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.DEACCESSIONED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseUserOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_forUnrestrictedReleasedFile() { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(false); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + assertTrue(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_forUnrestrictedReleasedActiveEmbargoFile() { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an embargo, an unrestricted file should only be accessible if the embargo has ended + + Embargo testEmbargo = new Embargo(LocalDate.now().plusDays(3), "Still embargoed"); + testDataFile.setEmbargo(testEmbargo); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(false); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + mockPermissionResponseRequestOn(false); + + assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_forUnrestrictedReleasedExpiredEmbargoFile() { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an embargo, an unrestricted file should only be accessible if the embargo has ended + + Embargo testEmbargo = new Embargo(LocalDate.now().minusDays(3), "Was embargoed"); + testDataFile.setEmbargo(testEmbargo); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(false); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + assertTrue(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forRestrictedReleasedFile(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(true); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseRequestOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forRestrictedReleasedFileWithActiveEmbargo(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an active embargo, a restricted file should have the same access regardless of + // embargo state (with an active embargo, there's no way to request permissions, + // so the hasPermission=true case primarily applies to the original dataset + // creators) + + Embargo testEmbargo = new Embargo(LocalDate.now().plusDays(3), "Still embargoed"); + testDataFile.setEmbargo(testEmbargo); + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(true); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseRequestOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forRestrictedReleasedFileWithExpiredEmbargo(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an embargo, a restricted file should have the same access regardless of + // embargo state (with an active embargo, there's no way to request permissions, + // so the hasPermission=true case primarily applies to the original dataset + // creators) + + Embargo testEmbargo = new Embargo(LocalDate.now().minusDays(3), "No longer embargoed"); + testDataFile.setEmbargo(testEmbargo); + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(true); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseRequestOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + private void mockPermissionResponseUserOn(boolean response) { + PermissionServiceBean.StaticPermissionQuery staticPermissionQueryMock = mock(PermissionServiceBean.StaticPermissionQuery.class); + + when(permissionServiceBeanStub.userOn(ArgumentMatchers.any(), ArgumentMatchers.any(Dataset.class))).thenReturn(staticPermissionQueryMock); + when(staticPermissionQueryMock.has(Permission.EditDataset)).thenReturn(response); + } + + private void mockPermissionResponseRequestOn(boolean response) { + PermissionServiceBean.RequestPermissionQuery requestPermissionQueryMock = mock(PermissionServiceBean.RequestPermissionQuery.class); + + when(permissionServiceBeanStub.requestOn(ArgumentMatchers.any(), ArgumentMatchers.any(DataFile.class))).thenReturn(requestPermissionQueryMock); + when(requestPermissionQueryMock.has(Permission.DownloadFile)).thenReturn(response); + } +} From 6ead834fe9ca1b7ad1ac1c5d83ff6e156df08255 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 7 Jul 2023 08:55:54 +0100 Subject: [PATCH 08/34] Added: canDataFileBeDownloaded API endpoint --- .../edu/harvard/iq/dataverse/api/Files.java | 15 +++++++-- .../edu/harvard/iq/dataverse/api/FilesIT.java | 32 +++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 ++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 467341f4077..341fb94e086 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -11,6 +11,7 @@ import edu.harvard.iq.dataverse.DataverseRequestServiceBean; import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.EjbDataverseEngine; +import edu.harvard.iq.dataverse.FileDownloadServiceBean; import edu.harvard.iq.dataverse.FileMetadata; import edu.harvard.iq.dataverse.GuestbookResponseServiceBean; import edu.harvard.iq.dataverse.TermsOfUseAndAccessValidator; @@ -106,6 +107,9 @@ public class Files extends AbstractApiBean { @Inject GuestbookResponseServiceBean guestbookResponseService; + @Inject + FileDownloadServiceBean fileDownloadServiceBean; + private static final Logger logger = Logger.getLogger(Files.class.getName()); @@ -824,11 +828,18 @@ public Response getFixityAlgorithm() { @GET @Path("{id}/guestbookResponses/count") - public Response getCountGuestbookResponsesByDataFileId(@PathParam("id") String dataFileId) { + public Response getCountGuestbookResponsesByDataFileId(@PathParam("id") long dataFileId) { try { - return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(Long.parseLong(dataFileId)).toString()); + return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFileId).toString()); } catch (NumberFormatException nfe) { return badRequest("File identifier has to be numeric."); } } + + @GET + @AuthRequired + @Path("{id}/canBeDownloaded") + public Response canDataFileBeDownloaded(@Context ContainerRequestContext crc, @PathParam("id") long dataFileId) { + return ok(fileDownloadServiceBean.canDownloadFile(getRequestUser(crc), fileSvc.find(dataFileId).getFileMetadata())); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 49b41d1e0e0..033ebe25062 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2054,4 +2054,36 @@ public void testGetCountGuestbookResponsesByDataFileId() { .statusCode(OK.getStatusCode()) .body("data.message", equalTo("1")); } + + @Test + public void testCanDataFileBeDownloaded() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + + // Upload test file + String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; + Response uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), pathToTestFile, Json.createObjectBuilder().build(), apiToken); + uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Publish collection and dataset + UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().assertThat().statusCode(OK.getStatusCode()); + UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode()); + + // Assert user can download test file + Integer testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + Response canDataFileBeDownloadedResponse = UtilIT.canDataFileBeDownloaded(testFileId, apiToken); + + canDataFileBeDownloadedResponse.then().assertThat().statusCode(OK.getStatusCode()); + boolean canDownloadTestFile = JsonPath.from(canDataFileBeDownloadedResponse.body().asString()).getBoolean("data"); + assertTrue(canDownloadTestFile); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 6c2543bcf82..4745cc7d2eb 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3275,4 +3275,10 @@ static Response getCountGuestbookResponsesByDataFileId(Integer dataFileId, Strin .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/guestbookResponses/count"); } + + static Response canDataFileBeDownloaded(Integer dataFileId, String apiToken) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/files/" + dataFileId + "/canBeDownloaded"); + } } From 9f35bf7843f1dc810e77217aec7bc1fe5dab17ad Mon Sep 17 00:00:00 2001 From: GPortas Date: Sun, 9 Jul 2023 15:35:49 +0100 Subject: [PATCH 09/34] Added: naming refactor and managing not found files in new files API endpoints --- .../iq/dataverse/FileDownloadServiceBean.java | 1 - .../edu/harvard/iq/dataverse/api/Files.java | 21 ++++++++++++------- .../edu/harvard/iq/dataverse/api/FilesIT.java | 20 +++++++++--------- .../edu/harvard/iq/dataverse/api/UtilIT.java | 8 ++----- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index 5c490b18ecc..f5bb60510cc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -574,7 +574,6 @@ public String getDirectStorageLocatrion(String storageLocation) { } /** - * * Checks if a user can download a file based on the file metadata and the permissions of the user * * This method is based on {@link edu.harvard.iq.dataverse.FileDownloadHelper#canDownloadFile(FileMetadata), diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 341fb94e086..d4bf28482c9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -106,7 +106,6 @@ public class Files extends AbstractApiBean { MakeDataCountLoggingServiceBean mdcLogService; @Inject GuestbookResponseServiceBean guestbookResponseService; - @Inject FileDownloadServiceBean fileDownloadServiceBean; @@ -828,18 +827,26 @@ public Response getFixityAlgorithm() { @GET @Path("{id}/guestbookResponses/count") - public Response getCountGuestbookResponsesByDataFileId(@PathParam("id") long dataFileId) { + public Response getCountGuestbookResponses(@PathParam("id") String dataFileId) { + DataFile dataFile; try { - return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFileId).toString()); - } catch (NumberFormatException nfe) { - return badRequest("File identifier has to be numeric."); + dataFile = findDataFileOrDie(dataFileId); + } catch (WrappedResponse wr) { + return wr.getResponse(); } + return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFile.getId()).toString()); } @GET @AuthRequired @Path("{id}/canBeDownloaded") - public Response canDataFileBeDownloaded(@Context ContainerRequestContext crc, @PathParam("id") long dataFileId) { - return ok(fileDownloadServiceBean.canDownloadFile(getRequestUser(crc), fileSvc.find(dataFileId).getFileMetadata())); + public Response canFileBeDownloaded(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { + DataFile dataFile; + try { + dataFile = findDataFileOrDie(dataFileId); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + return ok(fileDownloadServiceBean.canDownloadFile(getRequestUser(crc), dataFile.getFileMetadata())); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 033ebe25062..8cdc52be1d0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2020,7 +2020,7 @@ public void testDeleteFile() { } @Test - public void testGetCountGuestbookResponsesByDataFileId() { + public void testGetCountGuestbookResponses() { Response createUser = UtilIT.createRandomUser(); createUser.then().assertThat().statusCode(OK.getStatusCode()); String apiToken = UtilIT.getApiTokenFromResponse(createUser); @@ -2043,20 +2043,20 @@ public void testGetCountGuestbookResponsesByDataFileId() { UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode()); // Download test file - Integer testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); Response downloadResponse = UtilIT.downloadFile(testFileId, apiToken); downloadResponse.then().assertThat().statusCode(OK.getStatusCode()); // Get count guestbook responses and assert it is 1 - Response getGuestbookResponsesByDataFileIdResponse = UtilIT.getCountGuestbookResponsesByDataFileId(testFileId, apiToken); - getGuestbookResponsesByDataFileIdResponse.then().assertThat() + Response getCountGuestbookResponsesResponse = UtilIT.getCountGuestbookResponses(testFileId, apiToken); + getCountGuestbookResponsesResponse.then().assertThat() .statusCode(OK.getStatusCode()) .body("data.message", equalTo("1")); } @Test - public void testCanDataFileBeDownloaded() { + public void testCanFileBeDownloaded() { Response createUser = UtilIT.createRandomUser(); createUser.then().assertThat().statusCode(OK.getStatusCode()); String apiToken = UtilIT.getApiTokenFromResponse(createUser); @@ -2079,11 +2079,11 @@ public void testCanDataFileBeDownloaded() { UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode()); // Assert user can download test file - Integer testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - Response canDataFileBeDownloadedResponse = UtilIT.canDataFileBeDownloaded(testFileId, apiToken); + int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + Response canFileBeDownloadedResponse = UtilIT.canFileBeDownloaded(testFileId, apiToken); - canDataFileBeDownloadedResponse.then().assertThat().statusCode(OK.getStatusCode()); - boolean canDownloadTestFile = JsonPath.from(canDataFileBeDownloadedResponse.body().asString()).getBoolean("data"); - assertTrue(canDownloadTestFile); + canFileBeDownloadedResponse.then().assertThat().statusCode(OK.getStatusCode()); + boolean canFileBeDownloaded = JsonPath.from(canFileBeDownloadedResponse.body().asString()).getBoolean("data"); + assertTrue(canFileBeDownloaded); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 4745cc7d2eb..4f2ab3146ef 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1,6 +1,5 @@ package edu.harvard.iq.dataverse.api; -import com.jayway.restassured.RestAssured; import com.jayway.restassured.http.ContentType; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; @@ -12,8 +11,6 @@ import javax.json.JsonObjectBuilder; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -44,7 +41,6 @@ import static com.jayway.restassured.path.xml.XmlPath.from; import static com.jayway.restassured.RestAssured.given; import edu.harvard.iq.dataverse.DatasetField; -import edu.harvard.iq.dataverse.DatasetFieldConstant; import edu.harvard.iq.dataverse.DatasetFieldType; import edu.harvard.iq.dataverse.DatasetFieldValue; import edu.harvard.iq.dataverse.util.StringUtil; @@ -3270,13 +3266,13 @@ static Response createAndUploadTestFile(String persistentId, String testFileName return uploadZipFileViaSword(persistentId, pathToTestFile, apiToken); } - static Response getCountGuestbookResponsesByDataFileId(Integer dataFileId, String apiToken) { + static Response getCountGuestbookResponses(int dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/guestbookResponses/count"); } - static Response canDataFileBeDownloaded(Integer dataFileId, String apiToken) { + static Response canFileBeDownloaded(int dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/canBeDownloaded"); From a2bc4d4b1d98cb103706c49e1c357a3407b6c23b Mon Sep 17 00:00:00 2001 From: GPortas Date: Sun, 9 Jul 2023 15:47:08 +0100 Subject: [PATCH 10/34] Removed: not essential findDataFileOrDie call to avoid extra query --- src/main/java/edu/harvard/iq/dataverse/api/Files.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index d4bf28482c9..714a08f4d2c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -827,14 +827,8 @@ public Response getFixityAlgorithm() { @GET @Path("{id}/guestbookResponses/count") - public Response getCountGuestbookResponses(@PathParam("id") String dataFileId) { - DataFile dataFile; - try { - dataFile = findDataFileOrDie(dataFileId); - } catch (WrappedResponse wr) { - return wr.getResponse(); - } - return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFile.getId()).toString()); + public Response getCountGuestbookResponses(@PathParam("id") long dataFileId) { + return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFileId).toString()); } @GET From ec755348740e33b30f4909e78eee2e9f3c63f8a9 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 10 Jul 2023 09:11:22 +0100 Subject: [PATCH 11/34] Added: getFileThumbnailClass API endpoint and enhanced test coverage for new endpoints --- .../edu/harvard/iq/dataverse/api/Files.java | 15 ++++++ .../edu/harvard/iq/dataverse/api/FilesIT.java | 51 ++++++++++++++++--- .../edu/harvard/iq/dataverse/api/UtilIT.java | 8 ++- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 714a08f4d2c..2f5699d28ca 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -3,6 +3,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import edu.harvard.iq.dataverse.DataFile; +import edu.harvard.iq.dataverse.DataFileServiceBean; import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetLock; import edu.harvard.iq.dataverse.DatasetServiceBean; @@ -108,6 +109,8 @@ public class Files extends AbstractApiBean { GuestbookResponseServiceBean guestbookResponseService; @Inject FileDownloadServiceBean fileDownloadServiceBean; + @Inject + DataFileServiceBean dataFileServiceBean; private static final Logger logger = Logger.getLogger(Files.class.getName()); @@ -843,4 +846,16 @@ public Response canFileBeDownloaded(@Context ContainerRequestContext crc, @PathP } return ok(fileDownloadServiceBean.canDownloadFile(getRequestUser(crc), dataFile.getFileMetadata())); } + + @GET + @Path("{id}/thumbnailClass") + public Response getFileThumbnailClass(@PathParam("id") String dataFileId) { + DataFile dataFile; + try { + dataFile = findDataFileOrDie(dataFileId); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + return ok(dataFileServiceBean.getFileThumbnailClass(dataFile)); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 8cdc52be1d0..b3b96dc5234 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2020,7 +2020,7 @@ public void testDeleteFile() { } @Test - public void testGetCountGuestbookResponses() { + public void testGetCountGuestbookResponses() throws InterruptedException { Response createUser = UtilIT.createRandomUser(); createUser.then().assertThat().statusCode(OK.getStatusCode()); String apiToken = UtilIT.getApiTokenFromResponse(createUser); @@ -2048,6 +2048,9 @@ public void testGetCountGuestbookResponses() { Response downloadResponse = UtilIT.downloadFile(testFileId, apiToken); downloadResponse.then().assertThat().statusCode(OK.getStatusCode()); + // Ensure guestbook is updated + sleep(2000); + // Get count guestbook responses and assert it is 1 Response getCountGuestbookResponsesResponse = UtilIT.getCountGuestbookResponses(testFileId, apiToken); getCountGuestbookResponsesResponse.then().assertThat() @@ -2067,23 +2070,55 @@ public void testCanFileBeDownloaded() { Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); - Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); // Upload test file String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; - Response uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), pathToTestFile, Json.createObjectBuilder().build(), apiToken); + Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); - // Publish collection and dataset - UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().assertThat().statusCode(OK.getStatusCode()); - UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode()); - // Assert user can download test file int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - Response canFileBeDownloadedResponse = UtilIT.canFileBeDownloaded(testFileId, apiToken); + Response canFileBeDownloadedResponse = UtilIT.canFileBeDownloaded(Integer.toString(testFileId), apiToken); canFileBeDownloadedResponse.then().assertThat().statusCode(OK.getStatusCode()); boolean canFileBeDownloaded = JsonPath.from(canFileBeDownloadedResponse.body().asString()).getBoolean("data"); assertTrue(canFileBeDownloaded); + + // Call with invalid file id + Response canFileBeDownloadedInvalidIdResponse = UtilIT.canFileBeDownloaded("testInvalidId", apiToken); + canFileBeDownloadedInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); + } + + @Test + public void testGetFileThumbnailClass() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + + // Upload test file + String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; + Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); + uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Get file thumbnail class and assert is image + int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + Response getFileThumbnailClassResponse = UtilIT.getFileThumbnailClass(Integer.toString(testFileId), apiToken); + + getFileThumbnailClassResponse.then().assertThat().statusCode(OK.getStatusCode()); + String fileThumbnailClass = JsonPath.from(getFileThumbnailClassResponse.body().asString()).getString("data.message"); + assertEquals("image", fileThumbnailClass); + + // Call with invalid file id + Response getFileThumbnailClassInvalidIdResponse = UtilIT.getFileThumbnailClass("testInvalidId", apiToken); + getFileThumbnailClassInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 4f2ab3146ef..502a01f0d32 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3272,9 +3272,15 @@ static Response getCountGuestbookResponses(int dataFileId, String apiToken) { .get("/api/files/" + dataFileId + "/guestbookResponses/count"); } - static Response canFileBeDownloaded(int dataFileId, String apiToken) { + static Response canFileBeDownloaded(String dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/canBeDownloaded"); } + + static Response getFileThumbnailClass(String dataFileId, String apiToken) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/files/" + dataFileId + "/thumbnailClass"); + } } From 86865f5a979625e1f948c20d0618024f792e2c56 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 10 Jul 2023 12:48:08 +0100 Subject: [PATCH 12/34] Added: getCountGuestbookResponses PIDs support and param format and data file existence verifications --- src/main/java/edu/harvard/iq/dataverse/api/Files.java | 10 ++++++++-- .../java/edu/harvard/iq/dataverse/api/FilesIT.java | 6 +++++- src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 2f5699d28ca..171f8aa6e87 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -830,8 +830,14 @@ public Response getFixityAlgorithm() { @GET @Path("{id}/guestbookResponses/count") - public Response getCountGuestbookResponses(@PathParam("id") long dataFileId) { - return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFileId).toString()); + public Response getCountGuestbookResponses(@PathParam("id") String dataFileId) { + DataFile dataFile; + try { + dataFile = findDataFileOrDie(dataFileId); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFile.getId()).toString()); } @GET diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index b3b96dc5234..fe2007a37e4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2052,10 +2052,14 @@ public void testGetCountGuestbookResponses() throws InterruptedException { sleep(2000); // Get count guestbook responses and assert it is 1 - Response getCountGuestbookResponsesResponse = UtilIT.getCountGuestbookResponses(testFileId, apiToken); + Response getCountGuestbookResponsesResponse = UtilIT.getCountGuestbookResponses(Integer.toString(testFileId), apiToken); getCountGuestbookResponsesResponse.then().assertThat() .statusCode(OK.getStatusCode()) .body("data.message", equalTo("1")); + + // Call with invalid file id + Response getCountGuestbookResponsesInvalidIdResponse = UtilIT.getCountGuestbookResponses("testInvalidId", apiToken); + getCountGuestbookResponsesInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); } @Test diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 502a01f0d32..9a471648da4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3266,7 +3266,7 @@ static Response createAndUploadTestFile(String persistentId, String testFileName return uploadZipFileViaSword(persistentId, pathToTestFile, apiToken); } - static Response getCountGuestbookResponses(int dataFileId, String apiToken) { + static Response getCountGuestbookResponses(String dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/guestbookResponses/count"); From 489b990d63ab0a2ac4af02dd7725148134107a76 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 17 Jul 2023 13:59:03 +0100 Subject: [PATCH 13/34] Added: dataTables field to DataFile JSON API payload --- .../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 61134ae5245..38fedc70fc8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -693,7 +693,8 @@ public static JsonObjectBuilder json(DataFile df, FileMetadata fileMetadata, boo .add("checksum", getChecksumTypeAndValue(df.getChecksumType(), df.getChecksumValue())) .add("tabularTags", getTabularFileTags(df)) .add("creationDate", df.getCreateDateFormattedYYYYMMDD()) - .add("publicationDate", df.getPublicationDateFormattedYYYYMMDD()); + .add("publicationDate", df.getPublicationDateFormattedYYYYMMDD()) + .add("dataTables", df.getDataTables().isEmpty() ? null : JsonPrinter.jsonDT(df.getDataTables())); /* * The restricted state was not included prior to #9175 so to avoid backward * incompatability, it is now only added when generating json for the From 8c2781b1096a457ee824be57623dc0bc113da316 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 17 Jul 2023 14:09:12 +0100 Subject: [PATCH 14/34] Added: release notes for 9692 --- doc/release-notes/9692-files-api-extension.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 doc/release-notes/9692-files-api-extension.md diff --git a/doc/release-notes/9692-files-api-extension.md b/doc/release-notes/9692-files-api-extension.md new file mode 100644 index 00000000000..420da17d9b6 --- /dev/null +++ b/doc/release-notes/9692-files-api-extension.md @@ -0,0 +1,7 @@ +The following API endpoints have been added: + +- /api/files/{id}/guestbookResponses/count +- /api/files/{id}/canBeDownloaded +- /api/files/{id}/thumbnailClass + +The getVersionFiles endpoint (/api/datasets/{id}/versions/{versionId}/files) has been extended to support pagination and ordering From 867fb8adb48ab0c402ede1e472f5d014d6485b02 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 18 Jul 2023 14:42:57 +0100 Subject: [PATCH 15/34] Added: endpoint for getting file data tables and missing authentication added to other endpoints --- .../edu/harvard/iq/dataverse/api/Files.java | 43 ++++++++++------- .../iq/dataverse/util/json/JsonPrinter.java | 3 +- .../edu/harvard/iq/dataverse/api/FilesIT.java | 46 +++++++++++++++++-- .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 +++ src/test/resources/tab/test.tab | 16 +++++++ 5 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 src/test/resources/tab/test.tab diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 171f8aa6e87..f42f6f5abc7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -76,6 +76,8 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; + +import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonDT; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import javax.ws.rs.core.UriInfo; import org.glassfish.jersey.media.multipart.FormDataBodyPart; @@ -829,15 +831,13 @@ public Response getFixityAlgorithm() { } @GET + @AuthRequired @Path("{id}/guestbookResponses/count") - public Response getCountGuestbookResponses(@PathParam("id") String dataFileId) { - DataFile dataFile; - try { - dataFile = findDataFileOrDie(dataFileId); - } catch (WrappedResponse wr) { - return wr.getResponse(); - } - return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFile.getId()).toString()); + public Response getCountGuestbookResponses(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { + return response(req -> { + DataFile dataFile = execCommand(new GetDataFileCommand(req, findDataFileOrDie(dataFileId))); + return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFile.getId()).toString()); + }, getRequestUser(crc)); } @GET @@ -854,14 +854,25 @@ public Response canFileBeDownloaded(@Context ContainerRequestContext crc, @PathP } @GET + @AuthRequired @Path("{id}/thumbnailClass") - public Response getFileThumbnailClass(@PathParam("id") String dataFileId) { - DataFile dataFile; - try { - dataFile = findDataFileOrDie(dataFileId); - } catch (WrappedResponse wr) { - return wr.getResponse(); - } - return ok(dataFileServiceBean.getFileThumbnailClass(dataFile)); + public Response getFileThumbnailClass(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { + return response(req -> { + DataFile dataFile = execCommand(new GetDataFileCommand(req, findDataFileOrDie(dataFileId))); + return ok(dataFileServiceBean.getFileThumbnailClass(dataFile)); + }, getRequestUser(crc)); + } + + @GET + @AuthRequired + @Path("{id}/dataTables") + public Response getFileDataTables(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { + return response(req -> { + DataFile dataFile = execCommand(new GetDataFileCommand(req, findDataFileOrDie(dataFileId))); + if (!dataFile.isTabularData()) { + return error(BAD_REQUEST, "This operation is only available for tabular files."); + } + return ok(jsonDT(dataFile.getDataTables())); + }, getRequestUser(crc)); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 38fedc70fc8..61134ae5245 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -693,8 +693,7 @@ public static JsonObjectBuilder json(DataFile df, FileMetadata fileMetadata, boo .add("checksum", getChecksumTypeAndValue(df.getChecksumType(), df.getChecksumValue())) .add("tabularTags", getTabularFileTags(df)) .add("creationDate", df.getCreateDateFormattedYYYYMMDD()) - .add("publicationDate", df.getPublicationDateFormattedYYYYMMDD()) - .add("dataTables", df.getDataTables().isEmpty() ? null : JsonPrinter.jsonDT(df.getDataTables())); + .add("publicationDate", df.getPublicationDateFormattedYYYYMMDD()); /* * The restricted state was not included prior to #9175 so to avoid backward * incompatability, it is now only added when generating json for the diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index a2dafffea1b..67502d632fe 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2107,7 +2107,7 @@ public void testGetCountGuestbookResponses() throws InterruptedException { Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); // Upload test file - String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; + String pathToTestFile = "src/test/resources/images/coffeeshop.png"; Response uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), pathToTestFile, Json.createObjectBuilder().build(), apiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); @@ -2150,7 +2150,7 @@ public void testCanFileBeDownloaded() { int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); // Upload test file - String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; + String pathToTestFile = "src/test/resources/images/coffeeshop.png"; Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); @@ -2182,7 +2182,7 @@ public void testGetFileThumbnailClass() { int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); // Upload test file - String pathToTestFile = "src/main/webapp/resources/images/dataverseproject.png"; + String pathToTestFile = "src/test/resources/images/coffeeshop.png"; Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); @@ -2198,4 +2198,44 @@ public void testGetFileThumbnailClass() { Response getFileThumbnailClassInvalidIdResponse = UtilIT.getFileThumbnailClass("testInvalidId", apiToken); getFileThumbnailClassInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); } + + @Test + public void testGetFileDataTables() throws InterruptedException { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + + // Upload non-tabular file + String pathToNonTabularTestFile = "src/test/resources/images/coffeeshop.png"; + Response uploadNonTabularFileResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToNonTabularTestFile, Json.createObjectBuilder().build(), apiToken); + uploadNonTabularFileResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Assert that getting data tables for non-tabular file fails + int testNonTabularFileId = JsonPath.from(uploadNonTabularFileResponse.body().asString()).getInt("data.files[0].dataFile.id"); + Response getFileDataTablesForNonTabularFileResponse = UtilIT.getFileDataTables(Integer.toString(testNonTabularFileId), apiToken); + getFileDataTablesForNonTabularFileResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); + + // Upload tabular file + String pathToTabularTestFile = "src/test/resources/tab/test.tab"; + Response uploadTabularFileResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTabularTestFile, Json.createObjectBuilder().build(), apiToken); + uploadTabularFileResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Ensure tabular file is ingested + sleep(2000); + + // Get file data tables for the tabular file and assert data is obtained + int testTabularFileId = JsonPath.from(uploadTabularFileResponse.body().asString()).getInt("data.files[0].dataFile.id"); + Response getFileDataTablesForTabularFileResponse = UtilIT.getFileDataTables(Integer.toString(testTabularFileId), apiToken); + getFileDataTablesForTabularFileResponse.then().assertThat().statusCode(OK.getStatusCode()); + int dataTablesNumber = JsonPath.from(getFileDataTablesForTabularFileResponse.body().asString()).getList("data").size(); + assertTrue(dataTablesNumber > 0); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 831543ca484..de3df705714 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3295,4 +3295,10 @@ static Response getFileThumbnailClass(String dataFileId, String apiToken) { .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/thumbnailClass"); } + + static Response getFileDataTables(String dataFileId, String apiToken) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/files/" + dataFileId + "/dataTables"); + } } diff --git a/src/test/resources/tab/test.tab b/src/test/resources/tab/test.tab new file mode 100644 index 00000000000..628a309a530 --- /dev/null +++ b/src/test/resources/tab/test.tab @@ -0,0 +1,16 @@ +test1 test2 test3 test4 +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" From f2b374eaac45c13b34a545055a48bdad58605560 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 18 Jul 2023 14:45:53 +0100 Subject: [PATCH 16/34] Added: new endpoint to the release notes --- doc/release-notes/9692-files-api-extension.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/release-notes/9692-files-api-extension.md b/doc/release-notes/9692-files-api-extension.md index 420da17d9b6..03484083113 100644 --- a/doc/release-notes/9692-files-api-extension.md +++ b/doc/release-notes/9692-files-api-extension.md @@ -3,5 +3,6 @@ The following API endpoints have been added: - /api/files/{id}/guestbookResponses/count - /api/files/{id}/canBeDownloaded - /api/files/{id}/thumbnailClass +- /api/files/{id}/dataTables The getVersionFiles endpoint (/api/datasets/{id}/versions/{versionId}/files) has been extended to support pagination and ordering From 2cb77d40f4ee2a2d04f8f2578f420d2579b27564 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 25 Jul 2023 17:36:21 +0100 Subject: [PATCH 17/34] Changed: more realistic test tab file content --- src/test/resources/tab/test.tab | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/test/resources/tab/test.tab b/src/test/resources/tab/test.tab index 628a309a530..d750d42d995 100644 --- a/src/test/resources/tab/test.tab +++ b/src/test/resources/tab/test.tab @@ -1,16 +1,11 @@ -test1 test2 test3 test4 -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" +position name age +1 "Belle" 36 +2 "Lola" 37 +3 "Jayden" 45 +4 "Margaret" 37 +5 "Russell" 40 +6 "Bertie" 60 +7 "Maud" 34 +8 "Mabel" 31 +9 "Trevor" 51 +10 "Duane" 26 From c86a1d7a32107589f7cc86fca7d0be05525edbcf Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 25 Jul 2023 17:44:36 +0100 Subject: [PATCH 18/34] Refactor: file metadatas query formatting --- .../edu/harvard/iq/dataverse/DatasetVersionServiceBean.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 45edfb01777..687247156b2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -50,7 +50,9 @@ public class DatasetVersionServiceBean implements java.io.Serializable { private static final SimpleDateFormat logFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss"); - private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_LABEL = "SELECT fm FROM FileMetadata fm WHERE fm.datasetVersion.id=:datasetVersionId ORDER BY fm.label"; + private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_LABEL = "SELECT fm FROM FileMetadata fm" + + " WHERE fm.datasetVersion.id=:datasetVersionId" + + " ORDER BY fm.label"; private static final String QUERY_STR_FIND_ALL_FILE_METADATAS_ORDER_BY_DATE = "SELECT fm FROM FileMetadata fm, DvObject dvo" + " WHERE fm.datasetVersion.id = :datasetVersionId" + " AND fm.dataFile.id = dvo.id" From 991641b2f9355196dbab361752577c84eb5a8c39 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 25 Jul 2023 18:09:33 +0100 Subject: [PATCH 19/34] Refactor: using TypedQuery instead of Query for getFileMetadatas --- .../edu/harvard/iq/dataverse/DatasetVersionServiceBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 687247156b2..807ba94a0a6 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -1249,7 +1249,7 @@ public List getUnarchivedDatasetVersions(){ * @return a FileMetadata list of the specified DatasetVersion */ public List getFileMetadatas(DatasetVersion datasetVersion, Integer limit, Integer offset, FileMetadatasOrderCriteria orderCriteria) { - Query query = em.createQuery(getQueryStringFromFileMetadatasOrderCriteria(orderCriteria)) + TypedQuery query = em.createQuery(getQueryStringFromFileMetadatasOrderCriteria(orderCriteria), FileMetadata.class) .setParameter("datasetVersionId", datasetVersion.getId()); if (limit != null) { query.setMaxResults(limit); From 1ff9d905d69ba7d9c3b3858f2b6df95adcdddac9 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 27 Jul 2023 17:42:06 +0100 Subject: [PATCH 20/34] Refactor: getVersionFiles endpoint invalid orderCriteria error handling --- src/main/java/edu/harvard/iq/dataverse/api/Datasets.java | 4 +++- src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index a23cecd7180..623f4798a5b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -496,11 +496,13 @@ public Response getVersion(@Context ContainerRequestContext crc, @PathParam("id" public Response getVersionFiles(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, @PathParam("versionId") String versionId, @QueryParam("limit") Integer limit, @QueryParam("offset") Integer offset, @QueryParam("orderCriteria") String orderCriteria, @Context UriInfo uriInfo, @Context HttpHeaders headers) { return response( req -> { DatasetVersion datasetVersion = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers); + DatasetVersionServiceBean.FileMetadatasOrderCriteria fileMetadatasOrderCriteria; try { - return ok(jsonFileMetadatas(datasetversionService.getFileMetadatas(datasetVersion, limit, offset, orderCriteria != null ? DatasetVersionServiceBean.FileMetadatasOrderCriteria.valueOf(orderCriteria) : DatasetVersionServiceBean.FileMetadatasOrderCriteria.NameAZ))); + fileMetadatasOrderCriteria = orderCriteria != null ? DatasetVersionServiceBean.FileMetadatasOrderCriteria.valueOf(orderCriteria) : DatasetVersionServiceBean.FileMetadatasOrderCriteria.NameAZ; } catch (IllegalArgumentException e) { return error(Response.Status.BAD_REQUEST, "Invalid order criteria: " + orderCriteria); } + return ok(jsonFileMetadatas(datasetversionService.getFileMetadatas(datasetVersion, limit, offset, fileMetadatasOrderCriteria))); }, getRequestUser(crc)); } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 227d31e6733..282017462e4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -3386,5 +3386,12 @@ public void getVersionFiles() throws IOException { .body("data[2].label", equalTo(testFileName2)) .body("data[3].label", equalTo(testFileName3)) .body("data[4].label", equalTo(testFileName4)); + + // Test invalid order criteria + String invalidOrderCriteria = "invalidOrderCriteria"; + Response getVersionFilesResponseInvalidOrderCriteria = UtilIT.getVersionFiles(datasetId, testDatasetVersion, null, null, invalidOrderCriteria, apiToken); + getVersionFilesResponseInvalidOrderCriteria.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("message", equalTo("Invalid order criteria: " + invalidOrderCriteria)); } } From 7a9c2b383959b9608db429b7924eba387ac49a04 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 1 Aug 2023 16:28:06 +0100 Subject: [PATCH 21/34] Removed: canFileBeDownloaded endpoint and related logic --- .../iq/dataverse/FileDownloadServiceBean.java | 44 ---- .../edu/harvard/iq/dataverse/api/Files.java | 15 -- .../FileDownloadServiceBeanTest.java | 246 ------------------ .../edu/harvard/iq/dataverse/api/FilesIT.java | 32 --- .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 - 5 files changed, 343 deletions(-) delete mode 100644 src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index f5bb60510cc..18980ff9eff 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -572,48 +572,4 @@ public String getDirectStorageLocatrion(String storageLocation) { return null; } - - /** - * Checks if a user can download a file based on the file metadata and the permissions of the user - * - * This method is based on {@link edu.harvard.iq.dataverse.FileDownloadHelper#canDownloadFile(FileMetadata), - * and has been adapted to make it callable from the API instead of a view - * - * @param user requesting the download - * @param fileMetadata of the particular file to download - * @return boolean - */ - public boolean canDownloadFile(User user, FileMetadata fileMetadata){ - if (fileMetadata == null){ - return false; - } - if ((fileMetadata.getId() == null) || (fileMetadata.getDataFile().getId() == null)){ - return false; - } - if (user == null) { - return false; - } - if (user instanceof PrivateUrlUser) { - // Always allow download for PrivateUrlUser - return true; - } - - if (fileMetadata.getDatasetVersion().isDeaccessioned()) { - return this.permissionService.userOn(user, fileMetadata.getDatasetVersion().getDataset()).has(Permission.EditDataset); - } - - // Note that `isRestricted` at the FileMetadata level is for expressing intent by version. Enforcement is done with `isRestricted` at the DataFile level. - boolean isRestrictedFile = fileMetadata.isRestricted() || fileMetadata.getDataFile().isRestricted(); - if (!isRestrictedFile && !FileUtil.isActivelyEmbargoed(fileMetadata)){ - return true; - } - - // See if the DataverseRequest, which contains IP Groups, has permission to download the file. - if (permissionService.requestOn(dvRequestService.getDataverseRequest(), fileMetadata.getDataFile()).has(Permission.DownloadFile)) { - logger.fine("The DataverseRequest (User plus IP address) has access to download the file."); - return true; - } - - return false; - } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index f42f6f5abc7..83c464f2a27 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -110,8 +110,6 @@ public class Files extends AbstractApiBean { @Inject GuestbookResponseServiceBean guestbookResponseService; @Inject - FileDownloadServiceBean fileDownloadServiceBean; - @Inject DataFileServiceBean dataFileServiceBean; private static final Logger logger = Logger.getLogger(Files.class.getName()); @@ -840,19 +838,6 @@ public Response getCountGuestbookResponses(@Context ContainerRequestContext crc, }, getRequestUser(crc)); } - @GET - @AuthRequired - @Path("{id}/canBeDownloaded") - public Response canFileBeDownloaded(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { - DataFile dataFile; - try { - dataFile = findDataFileOrDie(dataFileId); - } catch (WrappedResponse wr) { - return wr.getResponse(); - } - return ok(fileDownloadServiceBean.canDownloadFile(getRequestUser(crc), dataFile.getFileMetadata())); - } - @GET @AuthRequired @Path("{id}/thumbnailClass") diff --git a/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java b/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java deleted file mode 100644 index d754dd357a9..00000000000 --- a/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java +++ /dev/null @@ -1,246 +0,0 @@ -package edu.harvard.iq.dataverse; - -import edu.harvard.iq.dataverse.authorization.Permission; -import edu.harvard.iq.dataverse.authorization.users.User; -import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.time.LocalDate; - -import static edu.harvard.iq.dataverse.mocks.MocksFactory.makeAuthenticatedUser; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -@ExtendWith(MockitoExtension.class) -public class FileDownloadServiceBeanTest { - - @Mock - private PermissionServiceBean permissionServiceBeanStub; - @Mock - private DataverseRequestServiceBean dataverseRequestServiceBeanMock; - @Mock - private MakeDataCountLoggingServiceBean makeDataCountLoggingServiceBeanMock; - - private FileDownloadServiceBean sut; - - private User testUser; - - @BeforeEach - public void setUp() { - sut = new FileDownloadServiceBean(); - sut.permissionService = permissionServiceBeanStub; - sut.dvRequestService = dataverseRequestServiceBeanMock; - sut.mdcLogService = makeDataCountLoggingServiceBeanMock; - testUser = makeAuthenticatedUser("Test", "Test"); - } - - @Test - public void testCanDownloadFile_withoutUser() { - assertFalse(sut.canDownloadFile(null, new FileMetadata())); - } - - @Test - public void testCanDownloadFile_withoutFileMetadata() { - assertFalse(sut.canDownloadFile(testUser, null)); - } - - @Test - void testCanDownloadFile_withNullMetadataId() { - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(null); - - assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); - } - - @Test - void testCanDownloadFile_withNullDataFileId() { - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - DataFile testDataFile = new DataFile(); - testDataFile.setId(null); - testFileMetadata.setDataFile(testDataFile); - - assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); - } - - @ParameterizedTest - @CsvSource({"false", "true"}) - void testCanDownloadFile_forDeaccessionedFile(boolean hasPermission) { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setDataset(new Dataset()); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.DEACCESSIONED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - - mockPermissionResponseUserOn(hasPermission); - - Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); - } - - @Test - void testCanDownloadFile_forUnrestrictedReleasedFile() { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setRestricted(false); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - - assertTrue(sut.canDownloadFile(testUser, testFileMetadata)); - } - - @Test - void testCanDownloadFile_forUnrestrictedReleasedActiveEmbargoFile() { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - // With an embargo, an unrestricted file should only be accessible if the embargo has ended - - Embargo testEmbargo = new Embargo(LocalDate.now().plusDays(3), "Still embargoed"); - testDataFile.setEmbargo(testEmbargo); - - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setRestricted(false); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - mockPermissionResponseRequestOn(false); - - assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); - } - - @Test - void testCanDownloadFile_forUnrestrictedReleasedExpiredEmbargoFile() { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - // With an embargo, an unrestricted file should only be accessible if the embargo has ended - - Embargo testEmbargo = new Embargo(LocalDate.now().minusDays(3), "Was embargoed"); - testDataFile.setEmbargo(testEmbargo); - - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setRestricted(false); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - - assertTrue(sut.canDownloadFile(testUser, testFileMetadata)); - } - - @ParameterizedTest - @CsvSource({"false", "true"}) - void testCanDownloadFile_forRestrictedReleasedFile(boolean hasPermission) { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setRestricted(true); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - - mockPermissionResponseRequestOn(hasPermission); - - Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); - } - - @ParameterizedTest - @CsvSource({"false", "true"}) - void testCanDownloadFile_forRestrictedReleasedFileWithActiveEmbargo(boolean hasPermission) { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - // With an active embargo, a restricted file should have the same access regardless of - // embargo state (with an active embargo, there's no way to request permissions, - // so the hasPermission=true case primarily applies to the original dataset - // creators) - - Embargo testEmbargo = new Embargo(LocalDate.now().plusDays(3), "Still embargoed"); - testDataFile.setEmbargo(testEmbargo); - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setRestricted(true); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - - mockPermissionResponseRequestOn(hasPermission); - - Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); - } - - @ParameterizedTest - @CsvSource({"false", "true"}) - void testCanDownloadFile_forRestrictedReleasedFileWithExpiredEmbargo(boolean hasPermission) { - DataFile testDataFile = new DataFile(); - testDataFile.setId(2L); - - // With an embargo, a restricted file should have the same access regardless of - // embargo state (with an active embargo, there's no way to request permissions, - // so the hasPermission=true case primarily applies to the original dataset - // creators) - - Embargo testEmbargo = new Embargo(LocalDate.now().minusDays(3), "No longer embargoed"); - testDataFile.setEmbargo(testEmbargo); - DatasetVersion testDatasetVersion = new DatasetVersion(); - testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); - - FileMetadata testFileMetadata = new FileMetadata(); - testFileMetadata.setId(1L); - testFileMetadata.setRestricted(true); - testFileMetadata.setDataFile(testDataFile); - testFileMetadata.setDatasetVersion(testDatasetVersion); - - mockPermissionResponseRequestOn(hasPermission); - - Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); - } - - private void mockPermissionResponseUserOn(boolean response) { - PermissionServiceBean.StaticPermissionQuery staticPermissionQueryMock = mock(PermissionServiceBean.StaticPermissionQuery.class); - - when(permissionServiceBeanStub.userOn(ArgumentMatchers.any(), ArgumentMatchers.any(Dataset.class))).thenReturn(staticPermissionQueryMock); - when(staticPermissionQueryMock.has(Permission.EditDataset)).thenReturn(response); - } - - private void mockPermissionResponseRequestOn(boolean response) { - PermissionServiceBean.RequestPermissionQuery requestPermissionQueryMock = mock(PermissionServiceBean.RequestPermissionQuery.class); - - when(permissionServiceBeanStub.requestOn(ArgumentMatchers.any(), ArgumentMatchers.any(DataFile.class))).thenReturn(requestPermissionQueryMock); - when(requestPermissionQueryMock.has(Permission.DownloadFile)).thenReturn(response); - } -} diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index b2f84bd3229..131beeec6b4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2145,38 +2145,6 @@ public void testGetCountGuestbookResponses() throws InterruptedException { getCountGuestbookResponsesInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); } - @Test - public void testCanFileBeDownloaded() { - Response createUser = UtilIT.createRandomUser(); - createUser.then().assertThat().statusCode(OK.getStatusCode()); - String apiToken = UtilIT.getApiTokenFromResponse(createUser); - - Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); - createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); - String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); - - Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); - createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); - int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); - - // Upload test file - String pathToTestFile = "src/test/resources/images/coffeeshop.png"; - Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); - uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); - - // Assert user can download test file - int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - Response canFileBeDownloadedResponse = UtilIT.canFileBeDownloaded(Integer.toString(testFileId), apiToken); - - canFileBeDownloadedResponse.then().assertThat().statusCode(OK.getStatusCode()); - boolean canFileBeDownloaded = JsonPath.from(canFileBeDownloadedResponse.body().asString()).getBoolean("data"); - assertTrue(canFileBeDownloaded); - - // Call with invalid file id - Response canFileBeDownloadedInvalidIdResponse = UtilIT.canFileBeDownloaded("testInvalidId", apiToken); - canFileBeDownloadedInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); - } - @Test public void testGetFileThumbnailClass() { Response createUser = UtilIT.createRandomUser(); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index de3df705714..feaf0635f22 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3284,12 +3284,6 @@ static Response getCountGuestbookResponses(String dataFileId, String apiToken) { .get("/api/files/" + dataFileId + "/guestbookResponses/count"); } - static Response canFileBeDownloaded(String dataFileId, String apiToken) { - return given() - .header(API_TOKEN_HTTP_HEADER, apiToken) - .get("/api/files/" + dataFileId + "/canBeDownloaded"); - } - static Response getFileThumbnailClass(String dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) From b52b33dc58962a8356892c497b186dccf63dd9d3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 2 Aug 2023 14:29:01 +0100 Subject: [PATCH 22/34] Added: getUserPermissionsOnFile endpoint to Access API --- .../iq/dataverse/FileDownloadServiceBean.java | 14 ++++++- .../edu/harvard/iq/dataverse/api/Access.java | 23 +++++++++-- .../harvard/iq/dataverse/api/AccessIT.java | 40 ++++++++++++++++++- .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 +++ 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index 18980ff9eff..bb25608033a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -8,6 +8,7 @@ import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.dataaccess.DataAccess; import edu.harvard.iq.dataverse.dataaccess.StorageIO; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; import edu.harvard.iq.dataverse.engine.command.impl.CreateGuestbookResponseCommand; import edu.harvard.iq.dataverse.engine.command.impl.RequestAccessCommand; @@ -18,7 +19,6 @@ import edu.harvard.iq.dataverse.privateurl.PrivateUrl; import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; -import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.FileUtil; import edu.harvard.iq.dataverse.util.StringUtil; import java.io.IOException; @@ -36,7 +36,6 @@ import javax.inject.Named; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import javax.persistence.Query; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; @@ -572,4 +571,15 @@ public String getDirectStorageLocatrion(String storageLocation) { return null; } + + /** + * Checks if the DataverseRequest, which contains IP Groups, has permission to download the file + * + * @param dataverseRequest the DataverseRequest + * @param dataFile the DataFile to check permissions + * @return boolean + */ + public boolean canDownloadFile(DataverseRequest dataverseRequest, DataFile dataFile) { + return permissionService.requestOn(dataverseRequest, dataFile).has(Permission.DownloadFile); + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index 02441a9ee11..5ea36188ec4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -31,7 +31,7 @@ import edu.harvard.iq.dataverse.RoleAssignment; import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.UserNotificationServiceBean; -import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; + import static edu.harvard.iq.dataverse.api.Datasets.handleVersion; import edu.harvard.iq.dataverse.api.auth.AuthRequired; @@ -121,8 +121,6 @@ import javax.ws.rs.core.StreamingOutput; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; import java.net.URISyntaxException; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.json.JsonObjectBuilder; import javax.ws.rs.RedirectionException; import javax.ws.rs.ServerErrorException; @@ -1946,5 +1944,22 @@ private URI handleCustomZipDownload(User user, String customZipServiceUrl, Strin throw new BadRequestException(); } return redirectUri; - } + } + + @GET + @AuthRequired + @Path("/datafile/{id}/userPermissions") + public Response getUserPermissionsOnFile(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { + DataFile dataFile; + try { + dataFile = findDataFileOrDie(dataFileId); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder(); + User requestUser = getRequestUser(crc); + jsonObjectBuilder.add("canDownloadFile", fileDownloadService.canDownloadFile(createDataverseRequest(requestUser), dataFile)); + jsonObjectBuilder.add("canEditOwnerDataset", permissionService.userOn(requestUser, dataFile.getOwner()).has(Permission.EditDataset)); + return ok(jsonObjectBuilder); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java index d6aac80b435..1fdbcab2cc4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java @@ -23,8 +23,12 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.HashMap; -import static javax.ws.rs.core.Response.Status.OK; + import org.hamcrest.collection.IsMapContaining; + +import javax.json.Json; + +import static javax.ws.rs.core.Response.Status.*; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; @@ -629,7 +633,39 @@ public void testZipUploadAndDownload() throws IOException { System.out.println("MD5 checksums of the unzipped file streams are correct."); System.out.println("Zip upload-and-download round trip test: success!"); - } + @Test + public void testGetUserPermissionsOnFile() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); + int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); + + // Upload test file + String pathToTestFile = "src/test/resources/images/coffeeshop.png"; + Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); + uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Assert user permissions on file + int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + Response getUserPermissionsOnFileResponse = UtilIT.getUserPermissionsOnFile(Integer.toString(testFileId), apiToken); + + getUserPermissionsOnFileResponse.then().assertThat().statusCode(OK.getStatusCode()); + boolean canDownloadFile = JsonPath.from(getUserPermissionsOnFileResponse.body().asString()).getBoolean("data.canDownloadFile"); + assertTrue(canDownloadFile); + boolean canEditOwnerDataset = JsonPath.from(getUserPermissionsOnFileResponse.body().asString()).getBoolean("data.canEditOwnerDataset"); + assertTrue(canEditOwnerDataset); + + // Call with invalid file id + Response getUserPermissionsOnFileInvalidIdResponse = UtilIT.getUserPermissionsOnFile("testInvalidId", apiToken); + getUserPermissionsOnFileInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index feaf0635f22..f39e246863d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3295,4 +3295,10 @@ static Response getFileDataTables(String dataFileId, String apiToken) { .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/files/" + dataFileId + "/dataTables"); } + + static Response getUserPermissionsOnFile(String dataFileId, String apiToken) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/access/datafile/" + dataFileId + "/userPermissions"); + } } From d16264d4d7c0f68c310b61189288b3021421ee05 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 4 Aug 2023 10:36:54 +0100 Subject: [PATCH 23/34] Removed: getFileThumbnailClass API endpoint --- .../edu/harvard/iq/dataverse/api/Files.java | 12 ------- .../edu/harvard/iq/dataverse/api/FilesIT.java | 32 ------------------- .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 ---- 3 files changed, 50 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 83c464f2a27..3ea7a6336b0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -109,8 +109,6 @@ public class Files extends AbstractApiBean { MakeDataCountLoggingServiceBean mdcLogService; @Inject GuestbookResponseServiceBean guestbookResponseService; - @Inject - DataFileServiceBean dataFileServiceBean; private static final Logger logger = Logger.getLogger(Files.class.getName()); @@ -838,16 +836,6 @@ public Response getCountGuestbookResponses(@Context ContainerRequestContext crc, }, getRequestUser(crc)); } - @GET - @AuthRequired - @Path("{id}/thumbnailClass") - public Response getFileThumbnailClass(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { - return response(req -> { - DataFile dataFile = execCommand(new GetDataFileCommand(req, findDataFileOrDie(dataFileId))); - return ok(dataFileServiceBean.getFileThumbnailClass(dataFile)); - }, getRequestUser(crc)); - } - @GET @AuthRequired @Path("{id}/dataTables") diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 131beeec6b4..e1cd6128fc0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2145,38 +2145,6 @@ public void testGetCountGuestbookResponses() throws InterruptedException { getCountGuestbookResponsesInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); } - @Test - public void testGetFileThumbnailClass() { - Response createUser = UtilIT.createRandomUser(); - createUser.then().assertThat().statusCode(OK.getStatusCode()); - String apiToken = UtilIT.getApiTokenFromResponse(createUser); - - Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); - createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); - String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); - - Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); - createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); - int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); - - // Upload test file - String pathToTestFile = "src/test/resources/images/coffeeshop.png"; - Response uploadResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToTestFile, Json.createObjectBuilder().build(), apiToken); - uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); - - // Get file thumbnail class and assert is image - int testFileId = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - Response getFileThumbnailClassResponse = UtilIT.getFileThumbnailClass(Integer.toString(testFileId), apiToken); - - getFileThumbnailClassResponse.then().assertThat().statusCode(OK.getStatusCode()); - String fileThumbnailClass = JsonPath.from(getFileThumbnailClassResponse.body().asString()).getString("data.message"); - assertEquals("image", fileThumbnailClass); - - // Call with invalid file id - Response getFileThumbnailClassInvalidIdResponse = UtilIT.getFileThumbnailClass("testInvalidId", apiToken); - getFileThumbnailClassInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); - } - @Test public void testGetFileDataTables() throws InterruptedException { Response createUser = UtilIT.createRandomUser(); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index f39e246863d..603e3be12cd 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3284,12 +3284,6 @@ static Response getCountGuestbookResponses(String dataFileId, String apiToken) { .get("/api/files/" + dataFileId + "/guestbookResponses/count"); } - static Response getFileThumbnailClass(String dataFileId, String apiToken) { - return given() - .header(API_TOKEN_HTTP_HEADER, apiToken) - .get("/api/files/" + dataFileId + "/thumbnailClass"); - } - static Response getFileDataTables(String dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) From 0952a62ebb47a1ae5d6fd14d64557e55fe82f45c Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 4 Aug 2023 11:11:58 +0100 Subject: [PATCH 24/34] Changed: release notes --- doc/release-notes/9692-files-api-extension.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/release-notes/9692-files-api-extension.md b/doc/release-notes/9692-files-api-extension.md index 03484083113..2dc441e7250 100644 --- a/doc/release-notes/9692-files-api-extension.md +++ b/doc/release-notes/9692-files-api-extension.md @@ -1,8 +1,7 @@ The following API endpoints have been added: - /api/files/{id}/guestbookResponses/count -- /api/files/{id}/canBeDownloaded -- /api/files/{id}/thumbnailClass - /api/files/{id}/dataTables +- /access/datafile/{id}/userPermissions The getVersionFiles endpoint (/api/datasets/{id}/versions/{versionId}/files) has been extended to support pagination and ordering From 02cecd4666c3093167f55245774a5f54e4dadf13 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 4 Aug 2023 11:12:49 +0100 Subject: [PATCH 25/34] Added: docs for files endpoint pagination and order criteria --- doc/sphinx-guides/source/api/native-api.rst | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 2bbcf108cad..8dad8ab13a9 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -958,6 +958,29 @@ The fully expanded example above (without environment variables) looks like this curl https://demo.dataverse.org/api/datasets/24/versions/1.0/files +This endpoint supports optional pagination, through the ``limit`` and ``offset`` query params: + +.. code-block:: bash + + curl "https://demo.dataverse.org/api/datasets/24/versions/1.0/files?limit=10&offset=20" + +Ordering criteria for sorting the results, like in the Dataverse UI, is also optionally supported. In particular, by the following possible values: + +* ``NameAZ`` (Default) +* ``NameZA`` +* ``Newest`` +* ``Oldest`` +* ``Size`` +* ``Type`` + +Please note that these values are case sensitive and must be correctly typed for the endpoint to recognize them. + +Usage example: + +.. code-block:: bash + + curl "https://demo.dataverse.org/api/datasets/24/versions/1.0/files?orderCriteria=Newest" + View Dataset Files and Folders as a Directory Index ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 6722777096fdf0262b7663add39b360277276d2d Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 4 Aug 2023 11:21:38 +0100 Subject: [PATCH 26/34] Added: docs for file dataTables API endpoint --- doc/sphinx-guides/source/api/native-api.rst | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 8dad8ab13a9..4b96efdb3ba 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -2725,6 +2725,44 @@ The fully expanded example above (without environment variables) looks like this Note: The ``id`` returned in the json response is the id of the file metadata version. +Getting File Data Tables +~~~~~~~~~~~~~~~~~~~~~~~~ + +This endpoint is oriented to tabular files, and provides a json representation of the file data tables for an existing tabular file. ``ID`` is the database id of the file to get the data tables from or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. + +A curl example using an ``ID`` + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export ID=24 + + curl $SERVER_URL/api/files/$ID/dataTables + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl https://demo.dataverse.org/api/files/24/dataTables + +A curl example using a ``PERSISTENT_ID`` + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export PERSISTENT_ID=doi:10.5072/FK2/AAA000 + + curl "$SERVER_URL/api/files/:persistentId/dataTables?persistentId=$PERSISTENT_ID" + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl "https://demo.dataverse.org/api/files/:persistentId/dataTables?persistentId=doi:10.5072/FK2/AAA000" + +Note that if the requested file is not tabular, the endpoint will return an error. Updating File Metadata ~~~~~~~~~~~~~~~~~~~~~~ From 90ef2f11e8e4a1dffe3bc1f632b7c55afed6bbd0 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 4 Aug 2023 11:28:47 +0100 Subject: [PATCH 27/34] Added: docs for guestbookResponses/count files API endpoint --- doc/sphinx-guides/source/api/native-api.rst | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 4b96efdb3ba..2bd8e3a02d6 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -2764,6 +2764,43 @@ The fully expanded example above (without environment variables) looks like this Note that if the requested file is not tabular, the endpoint will return an error. +Getting File Guestbook Response Count +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Provides the guestbook response count for a particular file, where ``ID`` is the database id of the file to get the count from or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. + +A curl example using an ``ID`` + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export ID=24 + + curl $SERVER_URL/api/files/$ID/guestbookResponses/count + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl https://demo.dataverse.org/api/files/24/guestbookResponses/count + +A curl example using a ``PERSISTENT_ID`` + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export PERSISTENT_ID=doi:10.5072/FK2/AAA000 + + curl "$SERVER_URL/api/files/:persistentId/guestbookResponses/count?persistentId=$PERSISTENT_ID" + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl "https://demo.dataverse.org/api/files/:persistentId/guestbookResponses/count?persistentId=doi:10.5072/FK2/AAA000" + Updating File Metadata ~~~~~~~~~~~~~~~~~~~~~~ From a7b2485b921b9acdba6287faca8c5e5ca2e438e1 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 4 Aug 2023 11:30:55 +0100 Subject: [PATCH 28/34] Changed: version files endpoint docs tweak --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 2bd8e3a02d6..1d8b4c987c2 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -964,7 +964,7 @@ This endpoint supports optional pagination, through the ``limit`` and ``offset`` curl "https://demo.dataverse.org/api/datasets/24/versions/1.0/files?limit=10&offset=20" -Ordering criteria for sorting the results, like in the Dataverse UI, is also optionally supported. In particular, by the following possible values: +Ordering criteria for sorting the results is also optionally supported. In particular, by the following possible values: * ``NameAZ`` (Default) * ``NameZA`` From cd8e2292f19b6e2f7c64fd34826a6e090428bd81 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 4 Aug 2023 10:44:12 -0400 Subject: [PATCH 29/34] wording #9692 --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 1d8b4c987c2..e14ed3a90c7 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -2728,7 +2728,7 @@ Note: The ``id`` returned in the json response is the id of the file metadata ve Getting File Data Tables ~~~~~~~~~~~~~~~~~~~~~~~~ -This endpoint is oriented to tabular files, and provides a json representation of the file data tables for an existing tabular file. ``ID`` is the database id of the file to get the data tables from or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. +This endpoint is oriented toward tabular files and provides a JSON representation of the file data tables for an existing tabular file. ``ID`` is the database id of the file to get the data tables from or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. A curl example using an ``ID`` From a64199fbda39c34215ae368b90f36a487ebcc651 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 9 Aug 2023 08:21:44 +0100 Subject: [PATCH 30/34] Added: API docs for access file user permissions --- doc/sphinx-guides/source/api/dataaccess.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/sphinx-guides/source/api/dataaccess.rst b/doc/sphinx-guides/source/api/dataaccess.rst index e76ea167587..2e5c368be47 100755 --- a/doc/sphinx-guides/source/api/dataaccess.rst +++ b/doc/sphinx-guides/source/api/dataaccess.rst @@ -403,3 +403,19 @@ This method returns a list of Authenticated Users who have requested access to t A curl example using an ``id``:: curl -H "X-Dataverse-key:$API_TOKEN" -X GET http://$SERVER/api/access/datafile/{id}/listRequests + +Get user permissions on a file: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``/api/access/datafile/{id}/userPermissions`` + +This method returns the permissions that the calling user has on a particular file. + +In particular, the user permissions that this method checks, returned as booleans, are the following: + +* Can download the file +* Can edit the file owner dataset + +A curl example using an ``id``:: + + curl -H "X-Dataverse-key:$API_TOKEN" -X GET http://$SERVER/api/access/datafile/{id}/userPermissions From 719fc67b0b93dc1f9a8fdc38e44dc1f7287f3944 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 9 Aug 2023 09:20:21 +0100 Subject: [PATCH 31/34] Changed: guestbookResponses/count endpoint renamed to downloadCount --- doc/release-notes/9692-files-api-extension.md | 2 +- doc/sphinx-guides/source/api/native-api.rst | 14 +++++++------- .../java/edu/harvard/iq/dataverse/api/Files.java | 4 ++-- .../java/edu/harvard/iq/dataverse/api/FilesIT.java | 14 +++++++------- .../java/edu/harvard/iq/dataverse/api/UtilIT.java | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/release-notes/9692-files-api-extension.md b/doc/release-notes/9692-files-api-extension.md index 2dc441e7250..baa8e2f87cd 100644 --- a/doc/release-notes/9692-files-api-extension.md +++ b/doc/release-notes/9692-files-api-extension.md @@ -1,6 +1,6 @@ The following API endpoints have been added: -- /api/files/{id}/guestbookResponses/count +- /api/files/{id}/downloadCount - /api/files/{id}/dataTables - /access/datafile/{id}/userPermissions diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index e14ed3a90c7..34a6717ada2 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -2764,10 +2764,10 @@ The fully expanded example above (without environment variables) looks like this Note that if the requested file is not tabular, the endpoint will return an error. -Getting File Guestbook Response Count -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Getting File Download Count +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Provides the guestbook response count for a particular file, where ``ID`` is the database id of the file to get the count from or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. +Provides the download count for a particular file, where ``ID`` is the database id of the file to get the download count from or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. A curl example using an ``ID`` @@ -2777,13 +2777,13 @@ A curl example using an ``ID`` export SERVER_URL=https://demo.dataverse.org export ID=24 - curl $SERVER_URL/api/files/$ID/guestbookResponses/count + curl $SERVER_URL/api/files/$ID/downloadCount The fully expanded example above (without environment variables) looks like this: .. code-block:: bash - curl https://demo.dataverse.org/api/files/24/guestbookResponses/count + curl https://demo.dataverse.org/api/files/24/downloadCount A curl example using a ``PERSISTENT_ID`` @@ -2793,13 +2793,13 @@ A curl example using a ``PERSISTENT_ID`` export SERVER_URL=https://demo.dataverse.org export PERSISTENT_ID=doi:10.5072/FK2/AAA000 - curl "$SERVER_URL/api/files/:persistentId/guestbookResponses/count?persistentId=$PERSISTENT_ID" + curl "$SERVER_URL/api/files/:persistentId/downloadCount?persistentId=$PERSISTENT_ID" The fully expanded example above (without environment variables) looks like this: .. code-block:: bash - curl "https://demo.dataverse.org/api/files/:persistentId/guestbookResponses/count?persistentId=doi:10.5072/FK2/AAA000" + curl "https://demo.dataverse.org/api/files/:persistentId/downloadCount?persistentId=doi:10.5072/FK2/AAA000" Updating File Metadata ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 3ea7a6336b0..d065c764bbe 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -828,8 +828,8 @@ public Response getFixityAlgorithm() { @GET @AuthRequired - @Path("{id}/guestbookResponses/count") - public Response getCountGuestbookResponses(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { + @Path("{id}/downloadCount") + public Response getFileDownloadCount(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { return response(req -> { DataFile dataFile = execCommand(new GetDataFileCommand(req, findDataFileOrDie(dataFileId))); return ok(guestbookResponseService.getCountGuestbookResponsesByDataFileId(dataFile.getId()).toString()); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index e1cd6128fc0..98fc6b55150 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2103,7 +2103,7 @@ public void testFilePIDsBehavior() { } @Test - public void testGetCountGuestbookResponses() throws InterruptedException { + public void testGetFileDownloadCount() throws InterruptedException { Response createUser = UtilIT.createRandomUser(); createUser.then().assertThat().statusCode(OK.getStatusCode()); String apiToken = UtilIT.getApiTokenFromResponse(createUser); @@ -2131,18 +2131,18 @@ public void testGetCountGuestbookResponses() throws InterruptedException { Response downloadResponse = UtilIT.downloadFile(testFileId, apiToken); downloadResponse.then().assertThat().statusCode(OK.getStatusCode()); - // Ensure guestbook is updated + // Ensure download count is updated sleep(2000); - // Get count guestbook responses and assert it is 1 - Response getCountGuestbookResponsesResponse = UtilIT.getCountGuestbookResponses(Integer.toString(testFileId), apiToken); - getCountGuestbookResponsesResponse.then().assertThat() + // Get download count and assert it is 1 + Response getFileDownloadCountResponse = UtilIT.getFileDownloadCount(Integer.toString(testFileId), apiToken); + getFileDownloadCountResponse.then().assertThat() .statusCode(OK.getStatusCode()) .body("data.message", equalTo("1")); // Call with invalid file id - Response getCountGuestbookResponsesInvalidIdResponse = UtilIT.getCountGuestbookResponses("testInvalidId", apiToken); - getCountGuestbookResponsesInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); + Response getFileDownloadCountInvalidIdResponse = UtilIT.getFileDownloadCount("testInvalidId", apiToken); + getFileDownloadCountInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); } @Test diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 603e3be12cd..2151a68f7da 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3278,10 +3278,10 @@ static Response createAndUploadTestFile(String persistentId, String testFileName return uploadZipFileViaSword(persistentId, pathToTestFile, apiToken); } - static Response getCountGuestbookResponses(String dataFileId, String apiToken) { + static Response getFileDownloadCount(String dataFileId, String apiToken) { return given() .header(API_TOKEN_HTTP_HEADER, apiToken) - .get("/api/files/" + dataFileId + "/guestbookResponses/count"); + .get("/api/files/" + dataFileId + "/downloadCount"); } static Response getFileDataTables(String dataFileId, String apiToken) { From 12243115d4e7effce29338a8dcfd8f4f7c111458 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 9 Aug 2023 10:07:01 -0400 Subject: [PATCH 32/34] doc tweaks #9692 --- doc/sphinx-guides/source/api/dataaccess.rst | 4 ++-- doc/sphinx-guides/source/api/metrics.rst | 7 +++++++ doc/sphinx-guides/source/api/native-api.rst | 8 +++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/sphinx-guides/source/api/dataaccess.rst b/doc/sphinx-guides/source/api/dataaccess.rst index 2e5c368be47..d714c90372a 100755 --- a/doc/sphinx-guides/source/api/dataaccess.rst +++ b/doc/sphinx-guides/source/api/dataaccess.rst @@ -404,7 +404,7 @@ A curl example using an ``id``:: curl -H "X-Dataverse-key:$API_TOKEN" -X GET http://$SERVER/api/access/datafile/{id}/listRequests -Get user permissions on a file: +Get User Permissions on a File: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``/api/access/datafile/{id}/userPermissions`` @@ -418,4 +418,4 @@ In particular, the user permissions that this method checks, returned as boolean A curl example using an ``id``:: - curl -H "X-Dataverse-key:$API_TOKEN" -X GET http://$SERVER/api/access/datafile/{id}/userPermissions + curl -H "X-Dataverse-key:$API_TOKEN" -X GET "http://$SERVER/api/access/datafile/{id}/userPermissions" diff --git a/doc/sphinx-guides/source/api/metrics.rst b/doc/sphinx-guides/source/api/metrics.rst index 28ac33ea228..613671e49d1 100755 --- a/doc/sphinx-guides/source/api/metrics.rst +++ b/doc/sphinx-guides/source/api/metrics.rst @@ -163,3 +163,10 @@ The following table lists the available metrics endpoints (not including the Mak /api/info/metrics/uniquefiledownloads/toMonth/{yyyy-MM},"count by id, pid","json, csv",collection subtree,published,y,cumulative up to month specified,unique download counts per file id to the specified month. PIDs are also included in output if they exist /api/info/metrics/tree,"id, ownerId, alias, depth, name, children",json,collection subtree,published,y,"tree of dataverses starting at the root or a specified parentAlias with their id, owner id, alias, name, a computed depth, and array of children dataverses","underlying code can also include draft dataverses, this is not currently accessible via api, depth starts at 0" /api/info/metrics/tree/toMonth/{yyyy-MM},"id, ownerId, alias, depth, name, children",json,collection subtree,published,y,"tree of dataverses in existence as of specified date starting at the root or a specified parentAlias with their id, owner id, alias, name, a computed depth, and array of children dataverses","underlying code can also include draft dataverses, this is not currently accessible via api, depth starts at 0" + +Related API Endpoints +--------------------- + +The following endpoints are not under the metrics namespace but also return counts: + +- :ref:`file-download-count` diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 34a6717ada2..236448de19c 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -956,7 +956,7 @@ The fully expanded example above (without environment variables) looks like this .. code-block:: bash - curl https://demo.dataverse.org/api/datasets/24/versions/1.0/files + curl "https://demo.dataverse.org/api/datasets/24/versions/1.0/files" This endpoint supports optional pagination, through the ``limit`` and ``offset`` query params: @@ -2764,6 +2764,8 @@ The fully expanded example above (without environment variables) looks like this Note that if the requested file is not tabular, the endpoint will return an error. +.. _file-download-count: + Getting File Download Count ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2777,13 +2779,13 @@ A curl example using an ``ID`` export SERVER_URL=https://demo.dataverse.org export ID=24 - curl $SERVER_URL/api/files/$ID/downloadCount + curl "$SERVER_URL/api/files/$ID/downloadCount" The fully expanded example above (without environment variables) looks like this: .. code-block:: bash - curl https://demo.dataverse.org/api/files/24/downloadCount + curl "https://demo.dataverse.org/api/files/24/downloadCount" A curl example using a ``PERSISTENT_ID`` From 35e454767288d191d496b04a2ee0f6471bebac53 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 16 Aug 2023 20:16:59 +0100 Subject: [PATCH 33/34] Fixed: missing import removed by mistake after develop merge --- src/main/java/edu/harvard/iq/dataverse/api/Files.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index cdf30fe9389..4c411a631f1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -76,6 +76,8 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; + +import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonDT; import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; import jakarta.ws.rs.core.UriInfo; import org.glassfish.jersey.media.multipart.FormDataBodyPart; From 2e2fb380aad84d92253064ab3c58c0293b7d6c8b Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 20 Sep 2023 05:53:29 +0100 Subject: [PATCH 34/34] Added: getFileDataTables endpoint permission checks for restricted and embargoed files --- .../edu/harvard/iq/dataverse/api/Files.java | 25 ++++++++++++++----- .../edu/harvard/iq/dataverse/api/FilesIT.java | 18 +++++++++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 4c411a631f1..fec60f10f3f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -18,6 +18,7 @@ import edu.harvard.iq.dataverse.TermsOfUseAndAccessValidator; import edu.harvard.iq.dataverse.UserNotificationServiceBean; import edu.harvard.iq.dataverse.api.auth.AuthRequired; +import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; @@ -79,6 +80,8 @@ import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonDT; import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; +import static jakarta.ws.rs.core.Response.Status.FORBIDDEN; + import jakarta.ws.rs.core.UriInfo; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; @@ -840,12 +843,22 @@ public Response getFileDownloadCount(@Context ContainerRequestContext crc, @Path @AuthRequired @Path("{id}/dataTables") public Response getFileDataTables(@Context ContainerRequestContext crc, @PathParam("id") String dataFileId) { - return response(req -> { - DataFile dataFile = execCommand(new GetDataFileCommand(req, findDataFileOrDie(dataFileId))); - if (!dataFile.isTabularData()) { - return error(BAD_REQUEST, "This operation is only available for tabular files."); + DataFile dataFile; + try { + dataFile = findDataFileOrDie(dataFileId); + } catch (WrappedResponse e) { + return error(Response.Status.NOT_FOUND, "File not found for given id."); + } + if (dataFile.isRestricted() || FileUtil.isActivelyEmbargoed(dataFile)) { + DataverseRequest dataverseRequest = createDataverseRequest(getRequestUser(crc)); + boolean hasPermissionToDownloadFile = permissionSvc.requestOn(dataverseRequest, dataFile).has(Permission.DownloadFile); + if (!hasPermissionToDownloadFile) { + return error(FORBIDDEN, "Insufficient permissions to access the requested information."); } - return ok(jsonDT(dataFile.getDataTables())); - }, getRequestUser(crc)); + } + if (!dataFile.isTabularData()) { + return error(BAD_REQUEST, "This operation is only available for tabular files."); + } + return ok(jsonDT(dataFile.getDataTables())); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index de91e5644cf..0a16bca7008 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -2190,11 +2190,25 @@ public void testGetFileDataTables() throws InterruptedException { // Ensure tabular file is ingested sleep(2000); + String testTabularFileId = Integer.toString(JsonPath.from(uploadTabularFileResponse.body().asString()).getInt("data.files[0].dataFile.id")); + // Get file data tables for the tabular file and assert data is obtained - int testTabularFileId = JsonPath.from(uploadTabularFileResponse.body().asString()).getInt("data.files[0].dataFile.id"); - Response getFileDataTablesForTabularFileResponse = UtilIT.getFileDataTables(Integer.toString(testTabularFileId), apiToken); + Response getFileDataTablesForTabularFileResponse = UtilIT.getFileDataTables(testTabularFileId, apiToken); getFileDataTablesForTabularFileResponse.then().assertThat().statusCode(OK.getStatusCode()); int dataTablesNumber = JsonPath.from(getFileDataTablesForTabularFileResponse.body().asString()).getList("data").size(); assertTrue(dataTablesNumber > 0); + + // Get file data tables for a restricted tabular file as the owner and assert data is obtained + Response restrictFileResponse = UtilIT.restrictFile(testTabularFileId, true, apiToken); + restrictFileResponse.then().assertThat().statusCode(OK.getStatusCode()); + getFileDataTablesForTabularFileResponse = UtilIT.getFileDataTables(testTabularFileId, apiToken); + getFileDataTablesForTabularFileResponse.then().assertThat().statusCode(OK.getStatusCode()); + + // Get file data tables for a restricted tabular file as other user and assert forbidden error is thrown + Response createRandomUser = UtilIT.createRandomUser(); + createRandomUser.then().assertThat().statusCode(OK.getStatusCode()); + String randomUserApiToken = UtilIT.getApiTokenFromResponse(createRandomUser); + getFileDataTablesForTabularFileResponse = UtilIT.getFileDataTables(testTabularFileId, randomUserApiToken); + getFileDataTablesForTabularFileResponse.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); } }