From 4a660f6484ead5571ee7c336142953c7b1217553 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:25:03 -0400 Subject: [PATCH 1/6] Fixed Anonymous guestbook file download via persistentId --- ...non-guestbook-download-via-persistentid.md | 4 +++ .../iq/dataverse/api/AbstractApiBean.java | 2 +- .../edu/harvard/iq/dataverse/api/Access.java | 26 ++++++++++++++++--- .../edu/harvard/iq/dataverse/api/FilesIT.java | 19 +++++++++++--- .../edu/harvard/iq/dataverse/api/UtilIT.java | 2 +- 5 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 doc/release-notes/12245-anon-guestbook-download-via-persistentid.md diff --git a/doc/release-notes/12245-anon-guestbook-download-via-persistentid.md b/doc/release-notes/12245-anon-guestbook-download-via-persistentid.md new file mode 100644 index 00000000000..b926a6441a1 --- /dev/null +++ b/doc/release-notes/12245-anon-guestbook-download-via-persistentid.md @@ -0,0 +1,4 @@ +## Bug +Guest requesting file download using :persistentId with guestbook response is now working. + +"persistentId" will be replaced by the actual fileId in the signed url that is returned by the POST call containing the guestbook response. diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index c2fcd9a144d..94785ce3976 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -77,7 +77,7 @@ public abstract class AbstractApiBean { private static final Logger logger = Logger.getLogger(AbstractApiBean.class.getName()); private static final String DATAVERSE_KEY_HEADER_NAME = "X-Dataverse-key"; - private static final String PERSISTENT_ID_KEY=":persistentId"; + protected static final String PERSISTENT_ID_KEY=":persistentId"; private static final String ALIAS_KEY=":alias"; public static final String STATUS_WF_IN_PROGRESS = "WORKFLOW_IN_PROGRESS"; public static final String DATAVERSE_WORKFLOW_INVOCATION_HEADER_NAME = "X-Dataverse-invocationID"; 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 5df583c4e72..12936509632 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -419,6 +419,11 @@ private String normalizeFileId(String fileId) { while (fId.lastIndexOf('/') == fId.length() - 1) { fId = fId.substring(0, fId.length() - 1); } + // Handle persistentId by converting it back to ID + if (fileId.equals(PERSISTENT_ID_KEY)) { + DataFile file = findDataFileOrDieWrapper(fileId); + fId = String.valueOf(file.getId()); + } if (fId.indexOf('/') > -1) { // This is for embedding folder names into the Access API URLs; @@ -448,7 +453,7 @@ private Response processDatafileWithGuestbookResponse(ContainerRequestContext cr // Handle Guestbook Responses String displayName = ""; String gbrids = null; - Long datasetId = null; + List fileIdList = new ArrayList<>(); try { // since all files must be in the same Dataset we can generate a Guestbook Response once and just replace the DataFile for each file in the list DataFile firstDatafile = datafilesMap.values().size() > 0 ? (DataFile) Arrays.stream(datafilesMap.values().toArray()).findFirst().get() : null; @@ -456,7 +461,7 @@ private Response processDatafileWithGuestbookResponse(ContainerRequestContext cr boolean guestbookResponseRequired = checkGuestbookRequiredResponse(crc, uriInfo, firstDatafile, null); for (DataFile df : datafilesMap.values()) { displayName = df.getDisplayName(); - datasetId = df.getOwner().getId(); + fileIdList.add(String.valueOf(df.getId())); if (guestbookResponseRequired) { if (gbr != null) { gbr.setDataFile(df); @@ -479,12 +484,13 @@ private Response processDatafileWithGuestbookResponse(ContainerRequestContext cr List args = Arrays.asList(displayName, ex.getLocalizedMessage()); return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.download.failure.guestbook.commandError", args)); } - return returnSignedUrl(crc, uriInfo, user, datasetId.toString(), gbrids); + return returnSignedUrl(crc, uriInfo, user, String.join(",", fileIdList), gbrids); } private Map getDatafilesMap(ContainerRequestContext crc, String fileIds) { String fileIdParams[] = getFileIdsCSV(fileIds); Map datafilesMap = new HashMap<>(); + Long datasetId = null; // Get and validate all the DataFiles first if (fileIdParams != null && fileIdParams.length > 0) { for (int i = 0; i < fileIdParams.length; i++) { @@ -496,6 +502,16 @@ private Map getDatafilesMap(ContainerRequestContext crc, String // (nobody should ever be using this API on a harvested DataFile)! } + // Make sure all files are from the same dataset + if (datasetId == null) { + datasetId = df.getOwner().getId(); + } else { + if (datasetId != df.getOwner().getId()) { + // All files must be from the same Dataset + throw new BadRequestException(BundleUtil.getStringFromBundle("access.api.download.failure.multipleDatasets")); + } + } + // This will throw a ForbiddenException if access isn't authorized: checkAuthorization(crc, df); @@ -526,7 +542,9 @@ private Response returnSignedUrl(ContainerRequestContext crc, UriInfo uriInfo, U } else { // Guest userIdentifier = "guest"; - key = uriInfo.getAbsolutePath().toASCIIString(); //TODO find a better one for here and in SignedUrlAuthMechanism.java + // Note: In order for the key to match we need to replace ":persistentId" with the actual file id since that is what will be sent in via the signed url. + key = URLDecoder.decode(uriInfo.getAbsolutePath().toASCIIString()) + .replace(":persistentId", id); //TODO find a better one for here and in SignedUrlAuthMechanism.java } UriBuilder builder = UriBuilder.fromUri(uriInfo.getRequestUri()); 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 1e06bd4eed4..403a8579b10 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -63,6 +63,11 @@ public static void setUpClass() { removePublicInstall.then().assertThat().statusCode(200); } + @AfterEach + public void resetClass() { + UtilIT.deleteSetting(SettingsServiceBean.Key.FilePIDsEnabled); + } + @AfterAll public static void tearDownClass() { UtilIT.deleteSetting(SettingsServiceBean.Key.PublicInstall); @@ -3869,6 +3874,7 @@ public void testUpdateWithEmptyFieldsAndVersionCheck() throws InterruptedExcepti @Test public void testDownloadFileWithGuestbookResponse() throws IOException, JsonParseException { msgt("testDownloadFileWithGuestbookResponse"); + UtilIT.enableSetting(SettingsServiceBean.Key.FilePIDsEnabled); // Create superuser Response createUserResponse = UtilIT.createRandomUser(); assertEquals(200, createUserResponse.getStatusCode()); @@ -3929,7 +3935,9 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars JsonObjectBuilder json4 = Json.createObjectBuilder().add("description", "my description4").add("directoryLabel", "data/subdir1").add("categories", Json.createArrayBuilder().add("Data")); uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/Robot-Icon_2.png", json1.build(), ownerApiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); + uploadResponse.prettyPrint(); Integer fileId4 = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + String filePersistentId = JsonPath.from(uploadResponse.body().asString()).getString("data.files[0].dataFile.persistentId"); // Restrict files Response restrictResponse = UtilIT.restrictFile(fileId1.toString(), true, ownerApiToken); @@ -4044,13 +4052,18 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars Response guestbookResponses = UtilIT.getGuestbookResponses(dataverseAlias, guestbook.getId(), ownerApiToken); assertTrue(guestbookResponses.prettyPrint().contains("My Name," + user2Email + ",My Institution,My Position")); - // Get Signed Download Url with guestbook response using persistentId - // POST /api/access/dataset/:persistentId?persistentId=doi:10.xxxx/FK2/ABC - downloadResponse = UtilIT.downloadFilesUrlWithGuestbookResponse(persistentId, apiToken, guestbookResponse); + // Get Signed Download Url for guest with guestbook response using file's persistentId + // POST /api/access/dataset/:persistentId?persistentId= + downloadResponse = UtilIT.downloadFilesUrlWithGuestbookResponse(filePersistentId, null, guestbookResponseForGuest); downloadResponse.prettyPrint(); downloadResponse.then().assertThat() .statusCode(OK.getStatusCode()); signedUrl = UtilIT.getSignedUrlFromResponse(downloadResponse); + // verify that the fileId is correct + assertTrue(signedUrl.contains("/access/datafile/" + fileId4 + "?")); + // verify that the persistentId is no longer in the url + assertFalse(signedUrl.contains("persistentId")); + // verify that the signed url is good signedUrlResponse = get(signedUrl); assertEquals(OK.getStatusCode(), signedUrlResponse.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 d70b8f2a235..f58f19e3141 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1302,7 +1302,7 @@ static Response downloadFilesUrlWithGuestbookResponse(String persistentId, Strin if (body != null) { requestSpecification.body(body); } - String getString = "/api/access/dataset/:persistentId?persistentId=" + persistentId; + String getString = "/api/access/datafile/:persistentId?persistentId=" + persistentId; return requestSpecification.post(getString); } From 460070ea4b5cfaf20516908af08b91afa77c5814 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:17:43 -0400 Subject: [PATCH 2/6] fix test --- .../edu/harvard/iq/dataverse/api/Access.java | 2 +- .../edu/harvard/iq/dataverse/api/FilesIT.java | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) 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 12936509632..4e1a6568518 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -506,7 +506,7 @@ private Map getDatafilesMap(ContainerRequestContext crc, String if (datasetId == null) { datasetId = df.getOwner().getId(); } else { - if (datasetId != df.getOwner().getId()) { + if (!datasetId.equals(df.getOwner().getId())) { // All files must be from the same Dataset throw new BadRequestException(BundleUtil.getStringFromBundle("access.api.download.failure.multipleDatasets")); } 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 403a8579b10..77661ccf350 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -3904,6 +3904,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode()); Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id"); String persistentId = JsonPath.from(createDatasetResponse.body().asString()).getString("data.persistentId"); + String directoryLabel = "data/store/" + persistentId.substring(4); Response getDatasetMetadata = UtilIT.nativeGet(datasetId, ownerApiToken); getDatasetMetadata.then().assertThat().statusCode(200); @@ -3920,20 +3921,23 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars assertEquals(1, getGuestbooksResponse.getBody().jsonPath().getList("data").size()); // Upload files - JsonObjectBuilder json1 = Json.createObjectBuilder().add("description", "my description1").add("directoryLabel", "data/subdir1").add("categories", Json.createArrayBuilder().add("Data")); + JsonObjectBuilder json1 = Json.createObjectBuilder().add("description", "my description1").add("directoryLabel", directoryLabel).add("categories", Json.createArrayBuilder().add("Data")); Response uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/dataverseproject.png", json1.build(), ownerApiToken); + uploadResponse.prettyPrint(); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); Integer fileId1 = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - JsonObjectBuilder json2 = Json.createObjectBuilder().add("description", "my description2").add("directoryLabel", "data/subdir1").add("categories", Json.createArrayBuilder().add("Data")); - uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/orcid_16x16.png", json1.build(), ownerApiToken); + JsonObjectBuilder json2 = Json.createObjectBuilder().add("description", "my description2").add("directoryLabel", directoryLabel).add("categories", Json.createArrayBuilder().add("Data")); + uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/orcid_16x16.png", json2.build(), ownerApiToken); + uploadResponse.prettyPrint(); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); Integer fileId2 = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - JsonObjectBuilder json3 = Json.createObjectBuilder().add("description", "my description3").add("directoryLabel", "data/subdir1").add("categories", Json.createArrayBuilder().add("Data")); - uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/cc0.png", json1.build(), ownerApiToken); + JsonObjectBuilder json3 = Json.createObjectBuilder().add("description", "my description3").add("directoryLabel", directoryLabel).add("categories", Json.createArrayBuilder().add("Data")); + uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/cc0.png", json3.build(), ownerApiToken); + uploadResponse.prettyPrint(); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); Integer fileId3 = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); - JsonObjectBuilder json4 = Json.createObjectBuilder().add("description", "my description4").add("directoryLabel", "data/subdir1").add("categories", Json.createArrayBuilder().add("Data")); - uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/Robot-Icon_2.png", json1.build(), ownerApiToken); + JsonObjectBuilder json4 = Json.createObjectBuilder().add("description", "my description4").add("directoryLabel", directoryLabel).add("categories", Json.createArrayBuilder().add("Data")); + uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/Robot-Icon_2.png", json4.build(), ownerApiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); uploadResponse.prettyPrint(); Integer fileId4 = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); From cc5dd50bbc539edd9ce6b1a93d16e7414ed49208 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:41:07 -0400 Subject: [PATCH 3/6] address comments --- src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 77661ccf350..0c29b0ac0d0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4057,7 +4057,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars assertTrue(guestbookResponses.prettyPrint().contains("My Name," + user2Email + ",My Institution,My Position")); // Get Signed Download Url for guest with guestbook response using file's persistentId - // POST /api/access/dataset/:persistentId?persistentId= + // POST /api/access/datafile/:persistentId?persistentId= downloadResponse = UtilIT.downloadFilesUrlWithGuestbookResponse(filePersistentId, null, guestbookResponseForGuest); downloadResponse.prettyPrint(); downloadResponse.then().assertThat() @@ -4158,6 +4158,7 @@ public void testGetFileCitationFormatted() { @Disabled public void testDownloadFileWithGuestbookResponseUsingBearerToken() throws IOException, JsonParseException { msgt("testDownloadFileWithGuestbookResponseUsingBearerToken"); + UtilIT.enableSetting(SettingsServiceBean.Key.FilePIDsEnabled); // Create superuser Response createUserResponse = UtilIT.createRandomUser(); assertEquals(200, createUserResponse.getStatusCode()); @@ -4183,7 +4184,7 @@ public void testDownloadFileWithGuestbookResponseUsingBearerToken() throws IOExc JsonObjectBuilder json1 = Json.createObjectBuilder().add("description", "my description1").add("directoryLabel", "data/subdir1").add("categories", Json.createArrayBuilder().add("Data")); Response uploadResponse = UtilIT.uploadFileViaNative(datasetId.toString(), "src/main/webapp/resources/images/dataverseproject.png", json1.build(), ownerApiToken); uploadResponse.then().assertThat().statusCode(OK.getStatusCode()); - Integer fileId1 = JsonPath.from(uploadResponse.body().asString()).getInt("data.files[0].dataFile.id"); + String filePersistentId = JsonPath.from(uploadResponse.body().asString()).getString("data.files[0].dataFile.persistentId"); // Publish dataverse and dataset Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, ownerApiToken); @@ -4237,7 +4238,7 @@ public void testDownloadFileWithGuestbookResponseUsingBearerToken() throws IOExc // POST with guestbook response String guestbookResponse = UtilIT.generateGuestbookResponse(guestbook).replace("\"guestbookResponse\": {", "\"guestbookResponse\": { \"name\":\"My Name\", \"position\":\"My Position\", \"institution\":\"My Institution\","); - Response downloadResponse = UtilIT.downloadFilesUrlWithGuestbookResponse(persistentId, null, guestbookResponse, userWithClaimsAccessToken); + Response downloadResponse = UtilIT.downloadFilesUrlWithGuestbookResponse(filePersistentId,null, guestbookResponse, userWithClaimsAccessToken); downloadResponse.prettyPrint(); downloadResponse.then().assertThat() .statusCode(OK.getStatusCode()); From 7b68d78f0331601fc847aedda565b11d3e1b2ffc Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:15:08 -0400 Subject: [PATCH 4/6] change signature-secret to signing-secret in config doc --- doc/sphinx-guides/source/installation/config.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index e5ed52acb83..215aec2cc3b 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -3308,9 +3308,9 @@ Can also be set via *MicroProfile Config API* sources, e.g. the environment vari **Note:** This setting was previously called `dataverse.personOrOrg.orgPhraseArray` and expected a JsonArray of strings. Please update both the name and value format if using the old setting. -.. _dataverse.api.signature-secret: +.. _dataverse.api.signing-secret: -dataverse.api.signature-secret +dataverse.api.signing-secret ++++++++++++++++++++++++++++++ Context: Dataverse has the ability to create "Signed URLs" for it's API calls. Using a signed URLs is more secure than @@ -3318,13 +3318,13 @@ providing API tokens, which are long-lived and give the holder all of the permis are time limited and only allow the action of the API call in the URL. See :ref:`api-exttools-auth` and :ref:`api-native-signed-url` for more details. -The key used to sign a URL is created from the API token of the creating user plus a signature-secret provided by an administrator. -**Using a signature-secret is highly recommended.** This setting defaults to an empty string. Using a non-empty -signature-secret makes it impossible for someone who knows an API token from forging signed URLs and provides extra security by +The key used to sign a URL is created from the API token of the creating user plus a signing-secret provided by an administrator. +**Using a signing-secret is highly recommended.** This setting defaults to an empty string. Using a non-empty +signing-secret makes it impossible for someone who knows an API token from forging signed URLs and provides extra security by making the overall signing key longer. **WARNING**: -*Since the signature-secret is sensitive, you should treat it like a password.* +*Since the signing-secret is sensitive, you should treat it like a password.* *See* :ref:`secure-password-storage` *to learn about ways to safeguard it.* Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_API_SIGNATURE_SECRET`` (although you shouldn't use environment variables for passwords) . From 55f77c5534bfb95348b3ecbb4f72335f2d579333 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:50:02 -0400 Subject: [PATCH 5/6] change signature-secret to signing-secret in native-api doc --- 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 8fe87ad5e65..83e8c401267 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -8842,7 +8842,7 @@ A curl example using allowing access to a dataset's metadata curl -H "X-Dataverse-key:$API_KEY" -H 'Content-Type:application/json' -d "$JSON" "$SERVER_URL/api/admin/requestSignedUrl" -Please see :ref:`dataverse.api.signature-secret` for the configuration option to add a shared secret, enabling extra +Please see :ref:`dataverse.api.signing-secret` for the configuration option to add a shared secret, enabling extra security. .. _send-feedback-admin: From 3ccee6e3d8d1a7eea20c8908aae7ab23af53ab05 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:11:23 -0400 Subject: [PATCH 6/6] fix download all files in dataset with guestbook response --- .../java/edu/harvard/iq/dataverse/api/Access.java | 8 +++++++- .../edu/harvard/iq/dataverse/api/FilesIT.java | 8 ++++++++ .../java/edu/harvard/iq/dataverse/api/UtilIT.java | 15 +++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) 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 4e1a6568518..c3c74f49019 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -454,9 +454,11 @@ private Response processDatafileWithGuestbookResponse(ContainerRequestContext cr String displayName = ""; String gbrids = null; List fileIdList = new ArrayList<>(); + String id = null; try { // since all files must be in the same Dataset we can generate a Guestbook Response once and just replace the DataFile for each file in the list DataFile firstDatafile = datafilesMap.values().size() > 0 ? (DataFile) Arrays.stream(datafilesMap.values().toArray()).findFirst().get() : null; + id = firstDatafile.getOwner().getId().toString(); GuestbookResponse gbr = getGuestbookResponseFromBody(firstDatafile, GuestbookResponse.DOWNLOAD, jsonBody, user); boolean guestbookResponseRequired = checkGuestbookRequiredResponse(crc, uriInfo, firstDatafile, null); for (DataFile df : datafilesMap.values()) { @@ -484,7 +486,11 @@ private Response processDatafileWithGuestbookResponse(ContainerRequestContext cr List args = Arrays.asList(displayName, ex.getLocalizedMessage()); return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.download.failure.guestbook.commandError", args)); } - return returnSignedUrl(crc, uriInfo, user, String.join(",", fileIdList), gbrids); + // Check if requesting datafile(s) or all files within dataset + if (!uriInfo.getPath().toLowerCase().contains("/dataset/")) { + id = String.join(",", fileIdList); + } + return returnSignedUrl(crc, uriInfo, user, id, gbrids); } private Map getDatafilesMap(ContainerRequestContext crc, String fileIds) { 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 0c29b0ac0d0..9b540ac8111 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4037,6 +4037,14 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars downloadResponse = UtilIT.postDownloadDatafiles(jsonBody, apiToken); assertEquals(OK.getStatusCode(), downloadResponse.getStatusCode()); + // Download all files in dataset with guestbook response using dataset persistentId + downloadResponse = UtilIT.downloadAllDatasetFilesWithGuestbookResponse(persistentId, apiToken, guestbookResponse); + downloadResponse.prettyPrint(); + assertEquals(OK.getStatusCode(), signedUrlResponse.getStatusCode()); + signedUrl = UtilIT.getSignedUrlFromResponse(downloadResponse); + signedUrlResponse = get(signedUrl); + assertEquals(OK.getStatusCode(), signedUrlResponse.getStatusCode()); + downloadResponse = UtilIT.downloadFilesUrlWithGuestbookResponse(new Integer[]{fileId1, fileId2, fileId3}, apiToken, guestbookResponse); signedUrl = UtilIT.getSignedUrlFromResponse(downloadResponse); signedUrlResponse = get(signedUrl); 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 f58f19e3141..e8dd00b1c2a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1302,8 +1302,19 @@ static Response downloadFilesUrlWithGuestbookResponse(String persistentId, Strin if (body != null) { requestSpecification.body(body); } - String getString = "/api/access/datafile/:persistentId?persistentId=" + persistentId; - return requestSpecification.post(getString); + String postString = "/api/access/datafile/:persistentId?persistentId=" + persistentId; + return requestSpecification.post(postString); + } + static Response downloadAllDatasetFilesWithGuestbookResponse(String persistentId, String apiToken, String body) { + RequestSpecification requestSpecification = given(); + if (apiToken != null) { + requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken); + } + if (body != null) { + requestSpecification.body(body); + } + String postString = "/api/access/dataset/:persistentId?persistentId=" + persistentId; + return requestSpecification.post(postString); } static Response postDownloadDatafiles(String body, String apiToken) {