Skip to content
Draft
43 changes: 31 additions & 12 deletions src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -5963,10 +5963,14 @@ public List<DatasetField> getDatasetSummaryFields() {
return DatasetUtil.getDatasetSummaryFields(workingVersion, customFields);
}

public boolean isShowPreviewButton(DataFile dataFile) {
List<ExternalTool> previewTools = getPreviewToolsForDataFile(dataFile);
return previewTools.size() > 0;
}
public boolean isShowPreviewButton(FileMetadata fmd) {
DataFile dataFile = fmd.getDataFile();
List<ExternalTool> previewTools = getPreviewToolsForDataFile(dataFile, fileDownloadHelper.canDownloadFile(fmd));
if (previewTools.isEmpty()) {
return false;
}
return true;
}

public boolean isShowQueryButton(DataFile dataFile) {

Expand All @@ -5977,27 +5981,37 @@ public boolean isShowQueryButton(DataFile dataFile) {
return false;
}

List<ExternalTool> fileQueryTools = getQueryToolsForDataFile(dataFile);
List<ExternalTool> fileQueryTools = getQueryToolsForDataFile(dataFile, true);
return fileQueryTools.size() > 0;
}

public List<ExternalTool> getPreviewToolsForDataFile(DataFile dataFile) {
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.PREVIEW);
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.PREVIEW, null);
}

public List<ExternalTool> getPreviewToolsForDataFile(DataFile dataFile, Boolean canDownload) {
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.PREVIEW, canDownload);
}

public List<ExternalTool> getQueryToolsForDataFile(DataFile dataFile) {
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.QUERY);
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.QUERY, null);
}

public List<ExternalTool> 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<ExternalTool> 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<ExternalTool> getExploreToolsForDataFile(DataFile dataFile) {
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.EXPLORE);
return getCachedToolsForDataFile(dataFile, ExternalTool.Type.EXPLORE, true);
}

public List<ExternalTool> getCachedToolsForDataFile(DataFile dataFile, ExternalTool.Type type) {
public List<ExternalTool> getCachedToolsForDataFile(DataFile dataFile, ExternalTool.Type type, Boolean canDownload) {
Long fileId = dataFile.getId();
Map<Long, List<ExternalTool>> cachedToolsByFileId = new HashMap<>();
List<ExternalTool> externalTools = new ArrayList<>();
Expand Down Expand Up @@ -6025,7 +6039,12 @@ public List<ExternalTool> 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;
}
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/FilePage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -372,10 +373,12 @@ public void setDatasetVersionId(Long datasetVersionId) {

// findPreviewTools would be a better name
private List<ExternalTool> sortExternalTools(){
boolean canDownload = fileDownloadHelper.canDownloadFile(fileMetadata);
List<ExternalTool> retList = new ArrayList<>();
List<ExternalTool> 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);
}
}
Expand Down
22 changes: 12 additions & 10 deletions src/main/java/edu/harvard/iq/dataverse/api/Files.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,44 +139,56 @@ 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<ExternalTool> findExternalToolsByFile(List<ExternalTool> allExternalTools, DataFile file) {
public List<ExternalTool> findExternalToolsByFile(List<ExternalTool> allExternalTools, DataFile file, boolean canDownload) {
List<ExternalTool> 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);
}
});

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) {
Expand Down
8 changes: 4 additions & 4 deletions src/main/webapp/file.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@
</ui:include>
</p:tab>
<p:tab id="previewTab" title="#{FilePage.toolTabTitle}"
rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0) and fileDownloadHelper.canDownloadFile(FilePage.fileMetadata)}">
rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0)}">
<!-- PREVIEW TERMS/GUESTBOOK FORM -->
<ui:fragment rendered="#{FilePage.guestbookAndTermsPopupRequired and !FilePage.termsMet and (FilePage.getSelectedTool().previewTool or FilePage.getSelectedTool().queryTool)}">
<ui:include id="previewGB" src="guestbook-terms-popup-fragment.xhtml">
Expand All @@ -382,7 +382,7 @@
<ui:fragment rendered="#{(!FilePage.guestbookAndTermsPopupRequired) or FilePage.termsMet}">
<div class="btn-toolbar margin-bottom" role="toolbar" aria-label="#{bundle['file.previewTab.button.label']}">
<!-- Preview Button Group -->
<div class="btn-group" jsf:rendered="#{FilePage.allAvailableTools.size() > 1 and fileDownloadHelper.canDownloadFile(FilePage.fileMetadata)}">
<div class="btn-group" jsf:rendered="#{FilePage.allAvailableTools.size() > 1}">
<button type="button" id="selectTool" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
#{bundle['file.toolsTab.button.label']} <span class="caret"></span>
</button>
Expand All @@ -400,7 +400,7 @@
</ui:fragment>
</div>
<!-- END: Preview Button Group -->
<div class="btn-group" jsf:rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0) and fileDownloadHelper.canDownloadFile(FilePage.fileMetadata)}">
<div class="btn-group" jsf:rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0)}">
<!-- Modular/Configured Explore Tool -->
<ui:remove><!-- Not sure this div will ever be rendered when terms are required and FilePage.termsMet isn't true. Leaving this logic in in case that is incorrect or changes going forward --></ui:remove>

Expand Down Expand Up @@ -433,7 +433,7 @@
</div>
</div>
<!-- FRAME EXTERNAL TOOL EMBED -->
<div id="previewPresentation" class="embed-responsive embed-responsive-16by9" jsf:rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0) and fileDownloadHelper.canDownloadFile(FilePage.fileMetadata)}">
<div id="previewPresentation" class="embed-responsive embed-responsive-16by9" jsf:rendered="#{(FilePage.toolsWithPreviews.size() > 0 or FilePage.queryTools.size() > 0)}">
<iframe role="presentation" title="#{bundle['file.previewTab.presentation']}" src="#{FilePage.preview(FilePage.selectedTool)}"></iframe>
</div>
</ui:fragment>
Expand Down
2 changes: 1 addition & 1 deletion src/main/webapp/filesFragment.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@
</a>
</ui:fragment>

<ui:fragment rendered="#{DatasetPage.isShowPreviewButton(fileMetadata.dataFile) and fileDownloadHelper.canDownloadFile(fileMetadata)}">
<ui:fragment rendered="#{DatasetPage.isShowPreviewButton(fileMetadata)}">
<a class="btn-preview btn btn-link bootstrap-button-tooltip" title="#{DatasetPage.getPreviewToolsForDataFile(fileMetadata.dataFile).get(0).getDisplayNameLang()}"
href="#{widgetWrapper.wrapURL('/file.xhtml?'.concat(!empty fileMetadata.dataFile.globalId ? 'persistentId=' : 'fileId=').concat(!empty fileMetadata.dataFile.globalId ? fileMetadata.dataFile.globalId.asString() : fileMetadata.dataFile.id).concat('&amp;version=').concat(fileMetadata.datasetVersion.friendlyVersionNumber)).concat('&amp;toolType=PREVIEW')}">
<span style="margin-top: 10px;" class="glyphicon glyphicon-eye-open"/><span class="sr-only">#{bundle.preview} "#{empty(fileMetadata.directoryLabel) ? "":fileMetadata.directoryLabel.concat("/")}#{fileMetadata.label}"</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void testfindAll() {
URLTokenUtil externalToolHandler4 = new ExternalToolHandler(externalTool, dataFile, apiToken, fmd, null);
List<ExternalTool> externalTools = new ArrayList<>();
externalTools.add(externalTool);
List<ExternalTool> availableExternalTools = externalToolService.findExternalToolsByFile(externalTools, dataFile);
List<ExternalTool> availableExternalTools = externalToolService.findExternalToolsByFile(externalTools, dataFile, true);
assertEquals(availableExternalTools.size(), 1);
}

Expand Down