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..5c2eaa05a9b --- /dev/null +++ b/doc/release-notes/12535-download-without-guestbook-response-for-preview-user.md @@ -0,0 +1,2 @@ +## Bug ## +Preview URL users could not download files from the dataset being previewed if a guestbook was assigned to that dataset. This is now fixed. diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index ce8e8b5f4bc..a80d4500ade 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,16 +15,19 @@ 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; 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; @@ -314,6 +318,14 @@ 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 = UrlSignerUtil.signUrl(getFullUrlForSigning(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); @@ -337,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/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index 7d12e5e8b3f..9cd0892fa48 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 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()); + } + 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/PrivateUrlServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlServiceBean.java index 01710e06f8f..3e5fd22631c 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; } /** @@ -59,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; } /**