diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index e79de5c1d8d..eab685a7eca 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -39,6 +39,7 @@ import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand; import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand; import edu.harvard.iq.dataverse.export.ExportService; +import edu.harvard.iq.dataverse.settings.FeatureFlags; import edu.harvard.iq.dataverse.util.cache.CacheFactoryBean; import edu.harvard.iq.dataverse.util.json.JsonUtil; import io.gdcc.spi.export.ExportException; @@ -163,7 +164,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; @@ -6000,9 +6000,13 @@ 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) { @@ -6014,27 +6018,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<>(); @@ -6062,7 +6076,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 09dc360e7be..698a9f94de6 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; @@ -378,10 +379,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 687ccadc36f..283a05931bb 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; @@ -231,7 +232,7 @@ public Response restrictFileInDataset(@Context ContainerRequestContext crc, @Tag(name = "replaceFilesInDataset", description = "Replace a file to a dataset") @RequestBody(description = "Multipart request containing replacement file content and JSON replacement metadata.", - content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA)) + content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA)) public Response replaceFileInDataset( @Context ContainerRequestContext crc, @Parameter(description = "Data file id or persistent identifier for the file being replaced.", required = true) @@ -989,10 +990,6 @@ public Response getExternalToolUrl(@Context ContainerRequestContext crc, @Parame ") 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); DataverseRequest req = createDataverseRequest(user); @@ -1007,14 +1004,18 @@ public Response getExternalToolUrl(@Context ContainerRequestContext crc, @Parame 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 50133aefa17..b31f8e82af3 100644 --- a/src/main/webapp/file.xhtml +++ b/src/main/webapp/file.xhtml @@ -362,7 +362,7 @@ + rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0)}"> @@ -383,7 +383,7 @@