diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index f72c818fdee..d139b7c5ec4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -102,6 +102,7 @@ import jakarta.faces.view.ViewScoped; import jakarta.inject.Inject; import jakarta.inject.Named; + import jakarta.persistence.OptimisticLockException; import org.apache.commons.lang3.StringUtils; @@ -157,7 +158,6 @@ import edu.harvard.iq.dataverse.search.SearchFields; import edu.harvard.iq.dataverse.search.SearchUtil; import edu.harvard.iq.dataverse.search.SolrClientService; -import edu.harvard.iq.dataverse.settings.FeatureFlags; import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.util.SignpostingResources; import edu.harvard.iq.dataverse.util.FileMetadataUtil; @@ -5963,10 +5963,14 @@ public List getDatasetSummaryFields() { return DatasetUtil.getDatasetSummaryFields(workingVersion, customFields); } - public boolean isShowPreviewButton(DataFile dataFile) { - List previewTools = getPreviewToolsForDataFile(dataFile); - return previewTools.size() > 0; - } + public boolean isShowPreviewButton(FileMetadata fmd) { + DataFile dataFile = fmd.getDataFile(); + List previewTools = getPreviewToolsForDataFile(dataFile, fileDownloadHelper.canDownloadFile(fmd)); + if (previewTools.isEmpty()) { + return false; + } + return true; + } public boolean isShowQueryButton(DataFile dataFile) { @@ -5977,27 +5981,37 @@ public boolean isShowQueryButton(DataFile dataFile) { return false; } - List fileQueryTools = getQueryToolsForDataFile(dataFile); + List fileQueryTools = getQueryToolsForDataFile(dataFile, true); return fileQueryTools.size() > 0; } public List getPreviewToolsForDataFile(DataFile dataFile) { - return getCachedToolsForDataFile(dataFile, ExternalTool.Type.PREVIEW); + return getCachedToolsForDataFile(dataFile, ExternalTool.Type.PREVIEW, null); + } + + public List getPreviewToolsForDataFile(DataFile dataFile, Boolean canDownload) { + return getCachedToolsForDataFile(dataFile, ExternalTool.Type.PREVIEW, canDownload); } public List getQueryToolsForDataFile(DataFile dataFile) { - return getCachedToolsForDataFile(dataFile, ExternalTool.Type.QUERY); + return getCachedToolsForDataFile(dataFile, ExternalTool.Type.QUERY, null); } + public List getQueryToolsForDataFile(DataFile dataFile, Boolean canDownload) { + return getCachedToolsForDataFile(dataFile, ExternalTool.Type.QUERY, canDownload); + } + + //ToDo: currently only shown if the user can Edit the dataset which means they can view files public List getConfigureToolsForDataFile(DataFile dataFile) { - return getCachedToolsForDataFile(dataFile, ExternalTool.Type.CONFIGURE); + return getCachedToolsForDataFile(dataFile, ExternalTool.Type.CONFIGURE, true); } + //ToDo: currently only shown if the user can Edit the dataset which means they can view files public List getExploreToolsForDataFile(DataFile dataFile) { - return getCachedToolsForDataFile(dataFile, ExternalTool.Type.EXPLORE); + return getCachedToolsForDataFile(dataFile, ExternalTool.Type.EXPLORE, true); } - public List getCachedToolsForDataFile(DataFile dataFile, ExternalTool.Type type) { + public List getCachedToolsForDataFile(DataFile dataFile, ExternalTool.Type type, Boolean canDownload) { Long fileId = dataFile.getId(); Map> cachedToolsByFileId = new HashMap<>(); List externalTools = new ArrayList<>(); @@ -6025,7 +6039,12 @@ public List getCachedToolsForDataFile(DataFile dataFile, ExternalT if (cachedTools != null) { //if already queried before and added to list return cachedTools; } - cachedTools = externalToolService.findExternalToolsByFile(externalTools, dataFile); + if(canDownload == null) { + logger.warning("Call to getCachedToolsForDataFile with null canDownload parameter and no cached results"); + //Return the set available to non-downloader in this case (versus an empty list) + canDownload = false; + } + cachedTools = externalToolService.findExternalToolsByFile(externalTools, dataFile, canDownload); cachedToolsByFileId.put(fileId, cachedTools); //add to map so we don't have to do the lifting again return cachedTools; } diff --git a/src/main/java/edu/harvard/iq/dataverse/FilePage.java b/src/main/java/edu/harvard/iq/dataverse/FilePage.java index b08598b2fb8..1829d31b548 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FilePage.java +++ b/src/main/java/edu/harvard/iq/dataverse/FilePage.java @@ -30,6 +30,7 @@ import edu.harvard.iq.dataverse.externaltools.ExternalTool; import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler; import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean; +import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean.RequirementStatus; import edu.harvard.iq.dataverse.ingest.IngestRequest; import edu.harvard.iq.dataverse.ingest.IngestServiceBean; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; @@ -372,10 +373,12 @@ public void setDatasetVersionId(Long datasetVersionId) { // findPreviewTools would be a better name private List sortExternalTools(){ + boolean canDownload = fileDownloadHelper.canDownloadFile(fileMetadata); List retList = new ArrayList<>(); List previewTools = externalToolService.findFileToolsByTypeAndContentType(ExternalTool.Type.PREVIEW, file.getContentType()); for (ExternalTool previewTool : previewTools) { - if (externalToolService.meetsRequirements(previewTool, file)) { + RequirementStatus status = externalToolService.meetsRequirements(previewTool, file, canDownload); + if (RequirementStatus.MET == status) { retList.add(previewTool); } } 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 0a1b19985a4..28a43bb98a8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -22,6 +22,7 @@ import io.gdcc.spi.export.ExportException; import edu.harvard.iq.dataverse.externaltools.ExternalTool; import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler; +import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean.RequirementStatus; import edu.harvard.iq.dataverse.ingest.IngestRequest; import edu.harvard.iq.dataverse.ingest.IngestServiceBean; import edu.harvard.iq.dataverse.ingest.IngestUtil; @@ -939,9 +940,6 @@ public Response getExternalToolUrl(@Context ContainerRequestContext crc, @PathPa ") does not match file content type (" + fileContentType + ")."); } - if (!externalToolService.meetsRequirements(externalTool, dataFile)) { - return error(BAD_REQUEST, "External tool requirements not met for this file."); - } // Get the current user and create a request object User user = getRequestUser(crc); @@ -957,14 +955,18 @@ public Response getExternalToolUrl(@Context ContainerRequestContext crc, @PathPa boolean isRestricted = dataFile.isRestricted() || fileMetadata.getDatasetVersion().isDraft() || FileUtil.isActivelyEmbargoed(fileMetadata) || fileMetadata.getDatasetVersion().isDeaccessioned() || FileUtil.isRetentionExpired(fileMetadata); - - // Check if user has permission to download the file if it's restricted + boolean hasPermission = true; if (isRestricted) { - boolean hasPermission = permissionSvc.requestOn(req, dataFile).has(Permission.DownloadFile); - if (!hasPermission) { - return error(Response.Status.FORBIDDEN, - "You do not have permission to access this file with the requested external tool."); - } + hasPermission = permissionSvc.requestOn(req, dataFile).has(Permission.DownloadFile); + } + RequirementStatus status = externalToolService.meetsRequirements(externalTool, dataFile, !isRestricted || hasPermission); + if (status == RequirementStatus.FILE_NOT_FOUND) { + return error(BAD_REQUEST, "External tool requirements not met for this file."); + } + if (status == RequirementStatus.INSUFFICIENT_PERMISSIONS) { + return error(Response.Status.FORBIDDEN, + "You do not have permission to access this file with the requested external tool."); + } // Determine if we need an API token for authentication diff --git a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java index 5ee0bf1355d..2f4f5ce386d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java @@ -139,14 +139,14 @@ public ExternalTool save(ExternalTool externalTool) { * file supports The list of tools is passed in so it doesn't hit the * database each time */ - public List findExternalToolsByFile(List allExternalTools, DataFile file) { + public List findExternalToolsByFile(List allExternalTools, DataFile file, boolean canDownload) { List externalTools = new ArrayList<>(); //Map tabular data to it's mimetype (the isTabularData() check assures that this code works the same as before, but it may need to change if tabular data is split into subtypes with differing mimetypes) final String contentType = file.isTabularData() ? DataFileServiceBean.MIME_TYPE_TSV_ALT : file.getContentType(); boolean isAccessible = StorageIO.isDataverseAccessible(DataAccess.getStorageDriverFromIdentifier(file.getStorageIdentifier())); allExternalTools.forEach((externalTool) -> { - //Match tool and file type, then check requirements - if (contentType.equals(externalTool.getContentType()) && meetsRequirements(externalTool, file) && (isAccessible || externalTool.accessesAuxFiles())) { + // Match tool and file type, then check requirements + if (contentType.equals(externalTool.getContentType()) && (RequirementStatus.MET == meetsRequirements(externalTool, file, canDownload)) && (isAccessible || externalTool.accessesAuxFiles())) { externalTools.add(externalTool); } }); @@ -154,29 +154,41 @@ public List findExternalToolsByFile(List allExternal return externalTools; } - public boolean meetsRequirements(ExternalTool externalTool, DataFile dataFile) { + public enum RequirementStatus { + MET, + FILE_NOT_FOUND, + INSUFFICIENT_PERMISSIONS + } + + public RequirementStatus meetsRequirements(ExternalTool externalTool, DataFile dataFile, boolean canDownload) { String requirements = externalTool.getRequirements(); if (requirements == null) { logger.fine("Data file id" + dataFile.getId() + ": no requirements for tool id " + externalTool.getId()); - return true; + return canDownload ? RequirementStatus.MET : RequirementStatus.INSUFFICIENT_PERMISSIONS; } - boolean meetsRequirements = true; + JsonObject requirementsObj = JsonUtil.getJsonObject(requirements); JsonArray auxFilesExist = requirementsObj.getJsonArray(ExternalTool.AUX_FILES_EXIST); + boolean permissionsMet = true; for (JsonValue jsonValue : auxFilesExist) { String formatTag = jsonValue.asJsonObject().getString("formatTag"); String formatVersion = jsonValue.asJsonObject().getString("formatVersion"); AuxiliaryFile auxFile = auxiliaryFileService.lookupAuxiliaryFile(dataFile, formatTag, formatVersion); if (auxFile == null) { logger.fine("Data file id" + dataFile.getId() + ": cannot find required aux file. formatTag=" + formatTag + ". formatVersion=" + formatVersion); - meetsRequirements = false; - break; + return RequirementStatus.FILE_NOT_FOUND; } else { logger.fine("Data file id" + dataFile.getId() + ": found required aux file. formatTag=" + formatTag + ". formatVersion=" + formatVersion); - meetsRequirements = true; + if (!auxFile.getIsPublic()) { + permissionsMet = false; + } } } - return meetsRequirements; + if (canDownload || permissionsMet) { + return RequirementStatus.MET; + } else { + return RequirementStatus.INSUFFICIENT_PERMISSIONS; + } } public static ExternalTool parseAddExternalToolManifest(String manifest) { diff --git a/src/main/webapp/file.xhtml b/src/main/webapp/file.xhtml index 2292ebf4c45..353957cd02f 100644 --- a/src/main/webapp/file.xhtml +++ b/src/main/webapp/file.xhtml @@ -361,7 +361,7 @@ + rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0)}"> @@ -382,7 +382,7 @@