diff --git a/doc/release-notes/11465-api-fetch-download-size-file-count.md b/doc/release-notes/11465-api-fetch-download-size-file-count.md new file mode 100644 index 00000000000..80b20eaa81d --- /dev/null +++ b/doc/release-notes/11465-api-fetch-download-size-file-count.md @@ -0,0 +1,4 @@ +The following APIs have now been blocked for Guest Users: + +/api/v1/datasets/:persistentId/versions/:latest-published/downloadsize?persistentId=doi:10.5072/FK2/VSAYEM&includeDeaccessioned=true&mode=Archival +/api/v1/datasets/:persistentId/versions/1.0/files/counts?persistentId=doi:10.5072/FK2/VSAYEM&includeDeaccessioned=true diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 293fc94638d..53684c4c67c 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1800,6 +1800,8 @@ The returned file counts are based on different criteria: - Per tabular tag name - Per access status (Possible values: Public, Restricted, EmbargoedThenRestricted, EmbargoedThenPublic, RetentionPeriodExpired) +Note: Authentication is required. This call will return a 403/Forbidden response for Guest users. + .. code-block:: bash export SERVER_URL=https://demo.dataverse.org @@ -2781,6 +2783,7 @@ Get the size of Downloading all the files of a Dataset Version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Shows the combined size in bytes of all the files available for download from version ``versionId`` of dataset ``id``. +Note: Authentication is required. This call will return a 403/Forbidden response for Guest users. .. code-block:: bash diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 94f51dd4ccc..392e9a80020 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -571,6 +571,11 @@ public Response getVersionFileCounts(@Context ContainerRequestContext crc, @QueryParam("includeDeaccessioned") boolean includeDeaccessioned, @Context UriInfo uriInfo, @Context HttpHeaders headers) { + try { + getRequestAuthenticatedUserOrDie(crc); + } catch (WrappedResponse e) { + return forbidden(BundleUtil.getStringFromBundle("datasets.api.version.files.invalid.auth")); + } return response(req -> { FileSearchCriteria fileSearchCriteria; try { @@ -3514,7 +3519,11 @@ public Response getDownloadSize(@Context ContainerRequestContext crc, @QueryParam("includeDeaccessioned") boolean includeDeaccessioned, @Context UriInfo uriInfo, @Context HttpHeaders headers) { - + try { + getRequestAuthenticatedUserOrDie(crc); + } catch (WrappedResponse e) { + return forbidden(BundleUtil.getStringFromBundle("datasets.api.version.files.invalid.auth")); + } return response(req -> { FileSearchCriteria fileSearchCriteria; try { diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 6560b914f56..88f3382ea7a 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -2833,6 +2833,7 @@ datasets.api.curationstatuscreatetime=Status Set Time datasets.api.curationstatussetter=Status Set By datasets.api.version.files.invalid.order.criteria=Invalid order criteria: {0} datasets.api.version.files.invalid.access.status=Invalid access status: {0} +datasets.api.version.files.invalid.auth=Only authenticated users can access this information. datasets.api.deaccessionDataset.invalid.version.identifier.error=Only {0} or a specific version can be deaccessioned datasets.api.deaccessionDataset.invalid.forward.url=Invalid deaccession forward URL: {0} datasets.api.globusdownloaddisabled=File transfer from Dataverse via Globus is not available for this dataset. diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 2decd7b19d7..f6e8e009aa8 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -5616,10 +5616,10 @@ public void getVersionFileCounts() throws IOException, InterruptedException { // Test that the dataset file counts for a deaccessioned dataset cannot be accessed by a guest // By latest published version Response getDatasetVersionResponse = UtilIT.getVersionFileCounts(datasetId, DS_VERSION_LATEST_PUBLISHED, null, null, null, null, null, true, null); - getDatasetVersionResponse.then().assertThat().statusCode(NOT_FOUND.getStatusCode()); + getDatasetVersionResponse.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); // By specific version 1.0 getDatasetVersionResponse = UtilIT.getVersionFileCounts(datasetId, "1.0", null, null, null, null, null, true, null); - getDatasetVersionResponse.then().assertThat().statusCode(NOT_FOUND.getStatusCode()); + getDatasetVersionResponse.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); } @Test @@ -5806,10 +5806,10 @@ public void getDownloadSize() throws IOException, InterruptedException { // Test that the dataset file counts for a deaccessioned dataset cannot be accessed by a guest // By latest published version Response getVersionFileCountsGuestUserResponse = UtilIT.getDownloadSize(datasetId, DS_VERSION_LATEST_PUBLISHED, null, null, null, null, null, DatasetVersionFilesServiceBean.FileDownloadSizeMode.All.toString(), true, null); - getVersionFileCountsGuestUserResponse.then().assertThat().statusCode(NOT_FOUND.getStatusCode()); + getVersionFileCountsGuestUserResponse.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); // By specific version 1.0 getVersionFileCountsGuestUserResponse = UtilIT.getDownloadSize(datasetId, "1.0", null, null, null, null, null, DatasetVersionFilesServiceBean.FileDownloadSizeMode.All.toString(), true, null); - getVersionFileCountsGuestUserResponse.then().assertThat().statusCode(NOT_FOUND.getStatusCode()); + getVersionFileCountsGuestUserResponse.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); } @Test diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 8a423f550ff..e42daaa18d5 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -1442,13 +1442,90 @@ public void testDataSizeInDataverse() throws InterruptedException { magicControlString = MessageFormat.format(BundleUtil.getStringFromBundle("datasets.api.datasize.download"), magicSizeNumber); - Response datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId.toString()); + Response datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId.toString(), apiTokenRando, null, null, null); datasetDownloadSizeResponse.prettyPrint(); assertEquals(magicControlString, JsonPath.from(datasetDownloadSizeResponse.body().asString()).getString("data.message")); } + @Test + public void testDeaccessionedDatasetGetDownloadSize() { + // Create user + String apiToken = createUserGetToken(); + // Create Dataverse + String dataverseAlias = createDataverseGetAlias(apiToken); + // Create Dataset + String datasetId1 = createDatasetGetId(dataverseAlias, apiToken).toString(); + String datasetId2 = createDatasetGetId(dataverseAlias, apiToken).toString(); + String pathToFile = "scripts/search/data/binary/trees.png"; + Response addResponse = UtilIT.uploadFileViaNative(datasetId1, pathToFile, apiToken); + + // Publish + UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken); + UtilIT.publishDatasetViaNativeApi(datasetId1, "major", apiToken); + UtilIT.publishDatasetViaNativeApi(datasetId2, "major", apiToken); + + // Test get sizes from Published Dataset with no files + Response datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId2, apiToken, null, Boolean.TRUE, null); + datasetDownloadSizeResponse.prettyPrint(); + datasetDownloadSizeResponse.then().assertThat() + .body("data.message", containsString("0 bytes")) + .body("data.storageSize", equalTo(0)) + .statusCode(OK.getStatusCode()); + // Test get files count from Published Dataset with no files + Response datasetFilesCountResponse = UtilIT.findDatasetFilesCount(datasetId2, apiToken, null, Boolean.TRUE); + datasetFilesCountResponse.prettyPrint(); + datasetFilesCountResponse.then().assertThat() + .body("data.total", equalTo(0)) + .statusCode(OK.getStatusCode()); + + // Deaccession the Dataset + UtilIT.deaccessionDataset(datasetId1, "1.0", "reason", null, apiToken).prettyPrint(); + UtilIT.deaccessionDataset(datasetId2, "1.0", "reason", null, apiToken).prettyPrint(); + + // Test get sizes from Deaccessioned Dataset with files (Auth user) + datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId1, apiToken, null, Boolean.TRUE, "Archival"); + datasetDownloadSizeResponse.prettyPrint(); + datasetDownloadSizeResponse.then().assertThat() + .body("data.message", containsString("8,361 bytes")) + .body("data.storageSize", equalTo(8361)) + .statusCode(OK.getStatusCode()); + // Test get sizes from Deaccessioned Dataset with files (Guest user) + datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId1, null, null, Boolean.TRUE, "Archival"); + datasetDownloadSizeResponse.prettyPrint(); + datasetDownloadSizeResponse.then().assertThat() + .statusCode(FORBIDDEN.getStatusCode()) + .body("message", equalTo(BundleUtil.getStringFromBundle("datasets.api.version.files.invalid.auth"))); + + // Test get sizes from Deaccessioned Dataset with no files (Auth user) + datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId2, apiToken, null, Boolean.TRUE, "Archival"); + datasetDownloadSizeResponse.prettyPrint(); + datasetDownloadSizeResponse.then().assertThat() + .body("data.message", containsString("0 bytes")) + .body("data.storageSize", equalTo(0)) + .statusCode(OK.getStatusCode()); + // Test get sizes from Deaccessioned Dataset with no files (Guest user) + datasetDownloadSizeResponse = UtilIT.findDatasetDownloadSize(datasetId2, null, null, Boolean.TRUE, "Archival"); + datasetDownloadSizeResponse.prettyPrint(); + datasetDownloadSizeResponse.then().assertThat() + .statusCode(FORBIDDEN.getStatusCode()) + .body("message", equalTo(BundleUtil.getStringFromBundle("datasets.api.version.files.invalid.auth"))); + + // Test get files count from Deaccessioned Dataset with no files (Auth user) + datasetFilesCountResponse = UtilIT.findDatasetFilesCount(datasetId2, apiToken, null, Boolean.TRUE); + datasetFilesCountResponse.prettyPrint(); + datasetFilesCountResponse.then().assertThat() + .body("data.total", equalTo(0)) + .statusCode(OK.getStatusCode()); + // Test get files count from Deaccessioned Dataset with no files (Guest user) + datasetFilesCountResponse = UtilIT.findDatasetFilesCount(datasetId2, null, null, Boolean.TRUE); + datasetFilesCountResponse.prettyPrint(); + datasetFilesCountResponse.then().assertThat() + .statusCode(FORBIDDEN.getStatusCode()) + .body("message", equalTo(BundleUtil.getStringFromBundle("datasets.api.version.files.invalid.auth"))); + } + @Test public void GetFileVersionDifferences() { // Create superuser and regular user diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 9bceb3919f8..971e5a740ef 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3691,7 +3691,54 @@ static Response findDatasetDownloadSize(String datasetId, String version, Strin .header(API_TOKEN_HTTP_HEADER, apiToken) .get("/api/datasets/" + datasetId + "/versions/" + version + "/downloadsize"); } - + + static Response findDatasetDownloadSize(String datasetId, String apiToken, String version, Boolean includeDeaccessioned, String mode) { + String id = datasetId; + if (version == null) { + version = ":latest-published"; + } + RequestSpecification requestSpecification = given(); + + if (datasetId.startsWith("doi:")) { + id = ":persistentId"; + requestSpecification.queryParam("persistentId", datasetId); + } + if (includeDeaccessioned != null) { + requestSpecification.queryParam("includeDeaccessioned", includeDeaccessioned); + } + if (mode != null) { + requestSpecification.queryParam("mode", mode); + } + + if (apiToken != null) { + requestSpecification.header(UtilIT.API_TOKEN_HTTP_HEADER, apiToken); + } + return requestSpecification + .get("/api/datasets/" + id + "/versions/" + version + "/downloadsize"); + } + + static Response findDatasetFilesCount(String datasetId, String apiToken, String version, Boolean includeDeaccessioned) { + String id = datasetId; + if (version == null) { + version = ":latest-published"; + } + RequestSpecification requestSpecification = given(); + + if (datasetId.startsWith("doi:")) { + id = ":persistentId"; + requestSpecification.queryParam("persistentId", datasetId); + } + if (includeDeaccessioned != null) { + requestSpecification.queryParam("includeDeaccessioned", includeDeaccessioned); + } + + if (apiToken != null) { + requestSpecification.header(UtilIT.API_TOKEN_HTTP_HEADER, apiToken); + } + return requestSpecification + .get("/api/datasets/" + id + "/versions/" + version + "/files/counts"); + } + static Response addBannerMessage(String pathToJsonFile) { String jsonIn = getDatasetJson(pathToJsonFile);