From 886a5082b2af8f9435b4bd9d0d1ac0a6e532c0a1 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 6 Jul 2023 12:27:40 +0100 Subject: [PATCH] Added: canDownloadFile method to FileDownloadServiceBean --- .../iq/dataverse/FileDownloadServiceBean.java | 46 +++- .../FileDownloadServiceBeanTest.java | 246 ++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java diff --git a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java index a90489be29a..5c490b18ecc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/FileDownloadServiceBean.java @@ -572,5 +572,49 @@ public String getDirectStorageLocatrion(String storageLocation) { return null; } - + + /** + * + * Checks if a user can download a file based on the file metadata and the permissions of the user + * + * This method is based on {@link edu.harvard.iq.dataverse.FileDownloadHelper#canDownloadFile(FileMetadata), + * and has been adapted to make it callable from the API instead of a view + * + * @param user requesting the download + * @param fileMetadata of the particular file to download + * @return boolean + */ + public boolean canDownloadFile(User user, FileMetadata fileMetadata){ + if (fileMetadata == null){ + return false; + } + if ((fileMetadata.getId() == null) || (fileMetadata.getDataFile().getId() == null)){ + return false; + } + if (user == null) { + return false; + } + if (user instanceof PrivateUrlUser) { + // Always allow download for PrivateUrlUser + return true; + } + + if (fileMetadata.getDatasetVersion().isDeaccessioned()) { + return this.permissionService.userOn(user, fileMetadata.getDatasetVersion().getDataset()).has(Permission.EditDataset); + } + + // Note that `isRestricted` at the FileMetadata level is for expressing intent by version. Enforcement is done with `isRestricted` at the DataFile level. + boolean isRestrictedFile = fileMetadata.isRestricted() || fileMetadata.getDataFile().isRestricted(); + if (!isRestrictedFile && !FileUtil.isActivelyEmbargoed(fileMetadata)){ + return true; + } + + // See if the DataverseRequest, which contains IP Groups, has permission to download the file. + if (permissionService.requestOn(dvRequestService.getDataverseRequest(), fileMetadata.getDataFile()).has(Permission.DownloadFile)) { + logger.fine("The DataverseRequest (User plus IP address) has access to download the file."); + return true; + } + + return false; + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java b/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java new file mode 100644 index 00000000000..d754dd357a9 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/FileDownloadServiceBeanTest.java @@ -0,0 +1,246 @@ +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.User; +import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; + +import static edu.harvard.iq.dataverse.mocks.MocksFactory.makeAuthenticatedUser; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +@ExtendWith(MockitoExtension.class) +public class FileDownloadServiceBeanTest { + + @Mock + private PermissionServiceBean permissionServiceBeanStub; + @Mock + private DataverseRequestServiceBean dataverseRequestServiceBeanMock; + @Mock + private MakeDataCountLoggingServiceBean makeDataCountLoggingServiceBeanMock; + + private FileDownloadServiceBean sut; + + private User testUser; + + @BeforeEach + public void setUp() { + sut = new FileDownloadServiceBean(); + sut.permissionService = permissionServiceBeanStub; + sut.dvRequestService = dataverseRequestServiceBeanMock; + sut.mdcLogService = makeDataCountLoggingServiceBeanMock; + testUser = makeAuthenticatedUser("Test", "Test"); + } + + @Test + public void testCanDownloadFile_withoutUser() { + assertFalse(sut.canDownloadFile(null, new FileMetadata())); + } + + @Test + public void testCanDownloadFile_withoutFileMetadata() { + assertFalse(sut.canDownloadFile(testUser, null)); + } + + @Test + void testCanDownloadFile_withNullMetadataId() { + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(null); + + assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_withNullDataFileId() { + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + DataFile testDataFile = new DataFile(); + testDataFile.setId(null); + testFileMetadata.setDataFile(testDataFile); + + assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forDeaccessionedFile(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setDataset(new Dataset()); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.DEACCESSIONED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseUserOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_forUnrestrictedReleasedFile() { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(false); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + assertTrue(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_forUnrestrictedReleasedActiveEmbargoFile() { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an embargo, an unrestricted file should only be accessible if the embargo has ended + + Embargo testEmbargo = new Embargo(LocalDate.now().plusDays(3), "Still embargoed"); + testDataFile.setEmbargo(testEmbargo); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(false); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + mockPermissionResponseRequestOn(false); + + assertFalse(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @Test + void testCanDownloadFile_forUnrestrictedReleasedExpiredEmbargoFile() { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an embargo, an unrestricted file should only be accessible if the embargo has ended + + Embargo testEmbargo = new Embargo(LocalDate.now().minusDays(3), "Was embargoed"); + testDataFile.setEmbargo(testEmbargo); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(false); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + assertTrue(sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forRestrictedReleasedFile(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(true); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseRequestOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forRestrictedReleasedFileWithActiveEmbargo(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an active embargo, a restricted file should have the same access regardless of + // embargo state (with an active embargo, there's no way to request permissions, + // so the hasPermission=true case primarily applies to the original dataset + // creators) + + Embargo testEmbargo = new Embargo(LocalDate.now().plusDays(3), "Still embargoed"); + testDataFile.setEmbargo(testEmbargo); + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(true); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseRequestOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + @ParameterizedTest + @CsvSource({"false", "true"}) + void testCanDownloadFile_forRestrictedReleasedFileWithExpiredEmbargo(boolean hasPermission) { + DataFile testDataFile = new DataFile(); + testDataFile.setId(2L); + + // With an embargo, a restricted file should have the same access regardless of + // embargo state (with an active embargo, there's no way to request permissions, + // so the hasPermission=true case primarily applies to the original dataset + // creators) + + Embargo testEmbargo = new Embargo(LocalDate.now().minusDays(3), "No longer embargoed"); + testDataFile.setEmbargo(testEmbargo); + DatasetVersion testDatasetVersion = new DatasetVersion(); + testDatasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED); + + FileMetadata testFileMetadata = new FileMetadata(); + testFileMetadata.setId(1L); + testFileMetadata.setRestricted(true); + testFileMetadata.setDataFile(testDataFile); + testFileMetadata.setDatasetVersion(testDatasetVersion); + + mockPermissionResponseRequestOn(hasPermission); + + Assertions.assertEquals(hasPermission, sut.canDownloadFile(testUser, testFileMetadata)); + } + + private void mockPermissionResponseUserOn(boolean response) { + PermissionServiceBean.StaticPermissionQuery staticPermissionQueryMock = mock(PermissionServiceBean.StaticPermissionQuery.class); + + when(permissionServiceBeanStub.userOn(ArgumentMatchers.any(), ArgumentMatchers.any(Dataset.class))).thenReturn(staticPermissionQueryMock); + when(staticPermissionQueryMock.has(Permission.EditDataset)).thenReturn(response); + } + + private void mockPermissionResponseRequestOn(boolean response) { + PermissionServiceBean.RequestPermissionQuery requestPermissionQueryMock = mock(PermissionServiceBean.RequestPermissionQuery.class); + + when(permissionServiceBeanStub.requestOn(ArgumentMatchers.any(), ArgumentMatchers.any(DataFile.class))).thenReturn(requestPermissionQueryMock); + when(requestPermissionQueryMock.has(Permission.DownloadFile)).thenReturn(response); + } +}