From 944924c9b6997f9d909c2b6ba9a6db830a853634 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:14:57 -0400 Subject: [PATCH 1/5] Allow Preview User to download files without requiring a Guestbook Response --- ...ad-without-guestbook-response-for-preview-user.md | 2 ++ .../iq/dataverse/FileDownloadServiceBean.java | 11 +++++++++++ .../java/edu/harvard/iq/dataverse/api/Access.java | 12 +++++++----- .../authorization/users/PrivateUrlUser.java | 12 ++++++++++-- .../iq/dataverse/privateurl/PrivateUrlPage.java | 1 + 5 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md diff --git a/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md b/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md new file mode 100644 index 00000000000..bc6ab7a603a --- /dev/null +++ b/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md @@ -0,0 +1,2 @@ +## Bug ## +Preview Users can now download files without the required guestbook response. diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index ce8e8b5f4bc..80886a462f9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -4,6 +4,7 @@ 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.PrivateUrlUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.dataaccess.DataAccess; import edu.harvard.iq.dataverse.dataaccess.StorageIO; @@ -14,6 +15,7 @@ import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean.MakeDataCountEntry; +import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.*; import jakarta.ejb.EJB; @@ -314,6 +316,15 @@ private void redirectToDownloadAPI(String downloadType, Long fileId, boolean gue } else { logger.fine("Redirecting to file download url: " + fileDownloadUrl); try { + // Sign URL for preview url user + User user = session.getUser(); + if (user != null && (user instanceof PrivateUrlUser)) { + PrivateUrlUser privateUrlUser = (PrivateUrlUser) user; + String key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + privateUrlUser.getToken(); + // Signing requires the full url path + fileDownloadUrl = fileDownloadUrl.replace("/api/access/", "http://localhost:8080/api/v1/access/"); + fileDownloadUrl = UrlSignerUtil.signUrl(fileDownloadUrl, 1, privateUrlUser.getIdentifier(), "GET", key); + } FacesContext.getCurrentInstance().getExternalContext().redirect(fileDownloadUrl); } catch (IOException ex) { logger.info("Failed to issue a redirect to file download url (" + fileDownloadUrl + "): " + ex); 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 7d12e5e8b3f..d72e84aa36c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -12,10 +12,7 @@ import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.authorization.RoleAssignee; -import edu.harvard.iq.dataverse.authorization.users.ApiToken; -import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; -import edu.harvard.iq.dataverse.authorization.users.GuestUser; -import edu.harvard.iq.dataverse.authorization.users.User; +import edu.harvard.iq.dataverse.authorization.users.*; import edu.harvard.iq.dataverse.dataaccess.*; import edu.harvard.iq.dataverse.datavariable.DataVariable; import edu.harvard.iq.dataverse.datavariable.VariableServiceBean; @@ -2237,7 +2234,12 @@ public Response getUserPermissionsOnFile(@Context ContainerRequestContext crc, private boolean checkGuestbookRequiredResponse(User user, UriInfo uriInfo, DataFile df, String gbrids) throws WebApplicationException { // Check if guestbook response is required Dataset d = df.getOwner(); - boolean required = df.getOwner().hasEnabledGuestbook() && !d.getEffectiveGuestbookEntryAtRequest(); + boolean exempt = false; + // PrivateUrlUser access to requested dataset's files must be checked! + if (user instanceof PrivateUrlUser) { + exempt = (df.getOwner().getId() == ((PrivateUrlUser) user).getDatasetId()); + } + boolean required = !exempt && df.getOwner().hasEnabledGuestbook() && !d.getEffectiveGuestbookEntryAtRequest(); boolean wasWrittenInPost = false; if (required) { User requestor = getRequestor(user); diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUser.java index 03f018221fd..546f4885466 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUser.java @@ -20,7 +20,8 @@ public class PrivateUrlUser implements User { * is a DvObject. */ private final long datasetId; - private final boolean anonymizedAccess; + private final boolean anonymizedAccess; + private String token = null; public PrivateUrlUser(long datasetId) { this(datasetId, false); @@ -38,7 +39,14 @@ public long getDatasetId() { public boolean hasAnonymizedAccess() { return anonymizedAccess; } - + + public String getToken() { + return token; + } + public void setToken(String token) { + this.token = token; + } + /** * By always returning false for isAuthenticated(), we prevent a * name from appearing in the corner as well as preventing an account page diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java index 17c622be9e2..7b5ac985b21 100644 --- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java @@ -52,6 +52,7 @@ public String init() { } if(!sessionUserCanViewUnpublishedDataset){ //Only Reset if user cannot view this Draft Version + privateUrlUser.setToken(token); session.setUser(privateUrlUser); } logger.info("Redirecting PrivateUrlUser '" + privateUrlUser.getIdentifier() + "' to " + draftDatasetPageToBeRedirectedTo); From 31434388e55f05f97a4cd00337946e6171f8b39e Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:48:31 -0400 Subject: [PATCH 2/5] comment fix for preview url user download access --- src/main/java/edu/harvard/iq/dataverse/api/Access.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d72e84aa36c..9cd0892fa48 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -2235,7 +2235,7 @@ private boolean checkGuestbookRequiredResponse(User user, UriInfo uriInfo, DataF // Check if guestbook response is required Dataset d = df.getOwner(); boolean exempt = false; - // PrivateUrlUser access to requested dataset's files must be checked! + // PrivateUrlUser access to draft files is exempt from guestbook responses in JSF https://github.com/IQSS/dataverse/issues/12535 if (user instanceof PrivateUrlUser) { exempt = (df.getOwner().getId() == ((PrivateUrlUser) user).getDatasetId()); } From 0503d9dfaaaf211c8bc9604b00d55c72b1a3f53c Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:00:40 -0400 Subject: [PATCH 3/5] fix release notes per review comment --- ...2535-download-without-guestbook-response-for-preview-user.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md b/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md index bc6ab7a603a..5c2eaa05a9b 100644 --- a/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md +++ b/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md @@ -1,2 +1,2 @@ ## Bug ## -Preview Users can now download files without the required guestbook response. +Preview URL users could not download files from the dataset being previewed if a guestbook was assigned to that dataset. This is now fixed. From 639854549395f0dc894662ecd4116bf2cca10a35 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:10:35 -0400 Subject: [PATCH 4/5] set token in privateUrlUser in PrivateUrlServiceBean --- .../edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java | 1 - .../iq/dataverse/privateurl/PrivateUrlServiceBean.java | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java index 7b5ac985b21..17c622be9e2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlPage.java @@ -52,7 +52,6 @@ public String init() { } if(!sessionUserCanViewUnpublishedDataset){ //Only Reset if user cannot view this Draft Version - privateUrlUser.setToken(token); session.setUser(privateUrlUser); } logger.info("Redirecting PrivateUrlUser '" + privateUrlUser.getIdentifier() + "' to " + draftDatasetPageToBeRedirectedTo); diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java index 01710e06f8f..ae35af8203d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java @@ -51,7 +51,11 @@ public PrivateUrl getPrivateUrlFromDatasetId(long datasetId) { * @return A PrivateUrlUser if one can be found using the token or null. */ public PrivateUrlUser getPrivateUrlUserFromToken(String token) { - return PrivateUrlUtil.getPrivateUrlUserFromRoleAssignment(getRoleAssignmentFromPrivateUrlToken(token)); + PrivateUrlUser user = PrivateUrlUtil.getPrivateUrlUserFromRoleAssignment(getRoleAssignmentFromPrivateUrlToken(token)); + if (user != null) { + user.setToken(token); + } + return user; } /** From 4477597b0d37b282165a4fc786195477948d9656 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:44:27 -0400 Subject: [PATCH 5/5] refactor based on review comment --- .../iq/dataverse/FileDownloadServiceBean.java | 25 ++++++++++++++++--- .../privateurl/PrivateUrlServiceBean.java | 6 ++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index 80886a462f9..a80d4500ade 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -20,12 +20,14 @@ import edu.harvard.iq.dataverse.util.*; import jakarta.ejb.EJB; import jakarta.ejb.Stateless; +import jakarta.faces.context.ExternalContext; import jakarta.faces.context.FacesContext; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.primefaces.PrimeFaces; @@ -319,11 +321,10 @@ private void redirectToDownloadAPI(String downloadType, Long fileId, boolean gue // Sign URL for preview url user User user = session.getUser(); if (user != null && (user instanceof PrivateUrlUser)) { - PrivateUrlUser privateUrlUser = (PrivateUrlUser) user; + PrivateUrlUser privateUrlUser = (PrivateUrlUser) user; String key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + privateUrlUser.getToken(); // Signing requires the full url path - fileDownloadUrl = fileDownloadUrl.replace("/api/access/", "http://localhost:8080/api/v1/access/"); - fileDownloadUrl = UrlSignerUtil.signUrl(fileDownloadUrl, 1, privateUrlUser.getIdentifier(), "GET", key); + fileDownloadUrl = UrlSignerUtil.signUrl(getFullUrlForSigning(fileDownloadUrl), 1, privateUrlUser.getIdentifier(), "GET", key); } FacesContext.getCurrentInstance().getExternalContext().redirect(fileDownloadUrl); } catch (IOException ex) { @@ -348,7 +349,23 @@ public void redirectToAuxFileDownloadAPI(Long fileId, String formatTag, String f logger.info("Failed to issue a redirect to aux file download url (" + fileDownloadUrl + "): " + ex); } } - + + private String getFullUrlForSigning(String path) { + ExternalContext extContext = FacesContext.getCurrentInstance().getExternalContext(); + + // Get the base server and port details + HttpServletRequest request = (HttpServletRequest) extContext.getRequest(); + String scheme = request.getScheme(); + String serverName = request.getServerName(); + int port = request.getServerPort(); + + // Build the full base URL + path and an API version (default is v1) + StringBuilder fullUrl = new StringBuilder(); + fullUrl.append(scheme).append("://").append(serverName).append(":").append(port) + .append(path.replace("/api/access/", "/api/v1/access/")); + + return fullUrl.toString(); + } /** * Launch an "explore" tool which is a type of ExternalTool such as * Data Explorer. This method may be invoked directly from the diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java index ae35af8203d..3e5fd22631c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java @@ -63,7 +63,11 @@ public PrivateUrlUser getPrivateUrlUserFromToken(String token) { * null. */ public PrivateUrlRedirectData getPrivateUrlRedirectDataFromToken(String token) { - return PrivateUrlUtil.getPrivateUrlRedirectData(getRoleAssignmentFromPrivateUrlToken(token)); + PrivateUrlRedirectData privateUrlRedirectData = PrivateUrlUtil.getPrivateUrlRedirectData(getRoleAssignmentFromPrivateUrlToken(token)); + if (privateUrlRedirectData != null && privateUrlRedirectData.getPrivateUrlUser() != null) { + privateUrlRedirectData.getPrivateUrlUser().setToken(token); + } + return privateUrlRedirectData; } /**