Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend the API to get Dataset user permissions, new File user permissions, and deaccessioned support for getDatasetVersion endpoint #10023

Merged
merged 8 commits into from
Oct 20, 2023
13 changes: 13 additions & 0 deletions doc/release-notes/10001-datasets-files-api-user-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- New query parameter `includeDeaccessioned` added to the getVersion endpoint (/api/datasets/{id}/versions/{versionId}) to consider deaccessioned versions when searching for versions.


- New endpoint to get user permissions on a dataset (/api/datasets/{id}/userPermissions). In particular, the user permissions that this API call checks, returned as booleans, are the following:

- Can view the unpublished dataset
- Can edit the dataset
- Can publish the dataset
- Can manage the dataset permissions
- Can delete the dataset draft


- New permission check "canManageFilePermissions" added to the existing endpoint for getting user permissions on a file (/api/access/datafile/{id}/userPermissions).
1 change: 1 addition & 0 deletions doc/sphinx-guides/source/api/dataaccess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ This method returns the permissions that the calling user has on a particular fi
In particular, the user permissions that this method checks, returned as booleans, are the following:

* Can download the file
* Can manage the file permissions
* Can edit the file owner dataset

A curl example using an ``id``::
Expand Down
30 changes: 30 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,16 @@ The fully expanded example above (without environment variables) looks like this

curl "https://demo.dataverse.org/api/datasets/24/versions/1.0"

By default, deaccessioned dataset versions are not included in the search when applying the :latest or :latest-published identifiers. Additionally, when filtering by a specific version tag, you will get a "not found" error if the version is deaccessioned and you do not enable the ``includeDeaccessioned`` option described below.

If you want to include deaccessioned dataset versions, you must set ``includeDeaccessioned`` query parameter to ``true``.

Usage example:

.. code-block:: bash

curl "https://demo.dataverse.org/api/datasets/24/versions/1.0?includeDeaccessioned=true"

.. _export-dataset-metadata-api:

Export Metadata of a Dataset in Various Formats
Expand Down Expand Up @@ -2496,6 +2506,26 @@ The API can also be used to reset the dataset to use the default/inherited value

curl -X DELETE -H "X-Dataverse-key:$API_TOKEN" -H Content-type:application/json "$SERVER_URL/api/datasets/:persistentId/guestbookEntryAtRequest?persistentId=$PERSISTENT_IDENTIFIER"

Get User Permissions on a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This API call returns the permissions that the calling user has on a particular dataset.

In particular, the user permissions that this API call checks, returned as booleans, are the following:

* Can view the unpublished dataset
* Can edit the dataset
* Can publish the dataset
* Can manage the dataset permissions
* Can delete the dataset draft

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=24

curl -H "X-Dataverse-key: $API_TOKEN" -X GET "$SERVER_URL/api/datasets/$ID/userPermissions"


Files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,15 +645,4 @@ public String getDirectStorageLocatrion(String storageLocation) {

return null;
}

/**
* Checks if the DataverseRequest, which contains IP Groups, has permission to download the file
*
* @param dataverseRequest the DataverseRequest
* @param dataFile the DataFile to check permissions
* @return boolean
*/
public boolean canDownloadFile(DataverseRequest dataverseRequest, DataFile dataFile) {
return permissionService.requestOn(dataverseRequest, dataFile).has(Permission.DownloadFile);
}
}
3 changes: 2 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/Access.java
Original file line number Diff line number Diff line change
Expand Up @@ -1709,7 +1709,8 @@ public Response getUserPermissionsOnFile(@Context ContainerRequestContext crc, @
}
JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
User requestUser = getRequestUser(crc);
jsonObjectBuilder.add("canDownloadFile", fileDownloadService.canDownloadFile(createDataverseRequest(requestUser), dataFile));
jsonObjectBuilder.add("canDownloadFile", permissionService.userOn(requestUser, dataFile).has(Permission.DownloadFile));
jsonObjectBuilder.add("canManageFilePermissions", permissionService.userOn(requestUser, dataFile).has(Permission.ManageFilePermissions));
jsonObjectBuilder.add("canEditOwnerDataset", permissionService.userOn(requestUser, dataFile.getOwner()).has(Permission.EditDataset));
return ok(jsonObjectBuilder);
}
Expand Down
29 changes: 27 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,14 @@ public Response listVersions(@Context ContainerRequestContext crc, @PathParam("i
@GET
@AuthRequired
@Path("{id}/versions/{versionId}")
public Response getVersion(@Context ContainerRequestContext crc, @PathParam("id") String datasetId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers) {
public Response getVersion(@Context ContainerRequestContext crc,
@PathParam("id") String datasetId,
@PathParam("versionId") String versionId,
@QueryParam("includeDeaccessioned") boolean includeDeaccessioned,
@Context UriInfo uriInfo,
@Context HttpHeaders headers) {
return response( req -> {
DatasetVersion dsv = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers);
DatasetVersion dsv = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(datasetId), uriInfo, headers, includeDeaccessioned);
return (dsv == null || dsv.getId() == null) ? notFound("Dataset version not found")
: ok(json(dsv));
}, getRequestUser(crc));
Expand Down Expand Up @@ -4083,4 +4088,24 @@ public Response resetGuestbookEntryAtRequest(@Context ContainerRequestContext cr
datasetService.merge(dataset);
return ok("Guestbook Entry At Request reset to default: " + dataset.getEffectiveGuestbookEntryAtRequest());
}

@GET
@AuthRequired
@Path("{id}/userPermissions")
public Response getUserPermissionsOnDataset(@Context ContainerRequestContext crc, @PathParam("id") String datasetId) {
Dataset dataset;
try {
dataset = findDatasetOrDie(datasetId);
} catch (WrappedResponse wr) {
return wr.getResponse();
}
User requestUser = getRequestUser(crc);
JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
jsonObjectBuilder.add("canViewUnpublishedDataset", permissionService.userOn(requestUser, dataset).has(Permission.ViewUnpublishedDataset));
jsonObjectBuilder.add("canEditDataset", permissionService.userOn(requestUser, dataset).has(Permission.EditDataset));
jsonObjectBuilder.add("canPublishDataset", permissionService.userOn(requestUser, dataset).has(Permission.PublishDataset));
jsonObjectBuilder.add("canManageDatasetPermissions", permissionService.userOn(requestUser, dataset).has(Permission.ManageDatasetPermissions));
jsonObjectBuilder.add("canDeleteDatasetDraft", permissionService.userOn(requestUser, dataset).has(Permission.DeleteDatasetDraft));
return ok(jsonObjectBuilder);
}
}
2 changes: 2 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ public void testGetUserPermissionsOnFile() {
assertTrue(canDownloadFile);
boolean canEditOwnerDataset = JsonPath.from(getUserPermissionsOnFileResponse.body().asString()).getBoolean("data.canEditOwnerDataset");
assertTrue(canEditOwnerDataset);
boolean canManageFilePermissions = JsonPath.from(getUserPermissionsOnFileResponse.body().asString()).getBoolean("data.canManageFilePermissions");
assertTrue(canManageFilePermissions);

// Call with invalid file id
Response getUserPermissionsOnFileInvalidIdResponse = UtilIT.getUserPermissionsOnFile("testInvalidId", apiToken);
Expand Down
47 changes: 46 additions & 1 deletion src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ public void testCreatePublishDestroyDataset() {
assertTrue(datasetContactFromExport.toString().contains("finch@mailinator.com"));
assertTrue(firstValue.toString().contains("finch@mailinator.com"));

Response getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST_PUBLISHED, apiToken);
Response getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST_PUBLISHED, false, apiToken);
getDatasetVersion.prettyPrint();
getDatasetVersion.then().assertThat()
.body("data.datasetId", equalTo(datasetId))
Expand Down Expand Up @@ -549,6 +549,18 @@ public void testCreatePublishDestroyDataset() {
}
assertEquals(datasetPersistentId, XmlPath.from(exportDatasetAsDdi.body().asString()).getString("codeBook.docDscr.citation.titlStmt.IDNo"));

// Test includeDeaccessioned option
Response deaccessionDatasetResponse = UtilIT.deaccessionDataset(datasetId, DS_VERSION_LATEST_PUBLISHED, "Test deaccession reason.", null, apiToken);
deaccessionDatasetResponse.then().assertThat().statusCode(OK.getStatusCode());

// includeDeaccessioned false
getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST_PUBLISHED, false, apiToken);
getDatasetVersion.then().assertThat().statusCode(NOT_FOUND.getStatusCode());

// includeDeaccessioned true
getDatasetVersion = UtilIT.getDatasetVersion(datasetPersistentId, DS_VERSION_LATEST_PUBLISHED, true, apiToken);
getDatasetVersion.then().assertThat().statusCode(OK.getStatusCode());

Response deleteDatasetResponse = UtilIT.destroyDataset(datasetId, apiToken);
deleteDatasetResponse.prettyPrint();
assertEquals(200, deleteDatasetResponse.getStatusCode());
Expand Down Expand Up @@ -3928,4 +3940,37 @@ public void getDownloadSize() throws IOException, InterruptedException {
getDownloadSizeResponse.then().assertThat().statusCode(OK.getStatusCode())
.body("data.storageSize", equalTo(expectedSizeIncludingAllSizes));
}

@Test
public void testGetUserPermissionsOnDataset() {
Response createUser = UtilIT.createRandomUser();
createUser.then().assertThat().statusCode(OK.getStatusCode());
String apiToken = UtilIT.getApiTokenFromResponse(createUser);

Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode());
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);

Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode());
int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id");

// Call with valid dataset id
Response getUserPermissionsOnDatasetResponse = UtilIT.getUserPermissionsOnDataset(Integer.toString(datasetId), apiToken);
getUserPermissionsOnDatasetResponse.then().assertThat().statusCode(OK.getStatusCode());
boolean canViewUnpublishedDataset = JsonPath.from(getUserPermissionsOnDatasetResponse.body().asString()).getBoolean("data.canViewUnpublishedDataset");
assertTrue(canViewUnpublishedDataset);
boolean canEditDataset = JsonPath.from(getUserPermissionsOnDatasetResponse.body().asString()).getBoolean("data.canEditDataset");
assertTrue(canEditDataset);
boolean canPublishDataset = JsonPath.from(getUserPermissionsOnDatasetResponse.body().asString()).getBoolean("data.canPublishDataset");
assertTrue(canPublishDataset);
boolean canManageDatasetPermissions = JsonPath.from(getUserPermissionsOnDatasetResponse.body().asString()).getBoolean("data.canManageDatasetPermissions");
assertTrue(canManageDatasetPermissions);
boolean canDeleteDatasetDraft = JsonPath.from(getUserPermissionsOnDatasetResponse.body().asString()).getBoolean("data.canDeleteDatasetDraft");
assertTrue(canDeleteDatasetDraft);

// Call with invalid dataset id
Response getUserPermissionsOnDatasetInvalidIdResponse = UtilIT.getUserPermissionsOnDataset("testInvalidId", apiToken);
getUserPermissionsOnDatasetInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode());
}
}
8 changes: 4 additions & 4 deletions src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -1989,14 +1989,14 @@ public void testDeleteFile() {
deleteResponse2.then().assertThat().statusCode(OK.getStatusCode());

// Check file 2 deleted from post v1.0 draft
Response postv1draft = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, apiToken);
Response postv1draft = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, false, apiToken);
postv1draft.prettyPrint();
postv1draft.then().assertThat()
.body("data.files.size()", equalTo(1))
.statusCode(OK.getStatusCode());

// Check file 2 still in v1.0
Response v1 = UtilIT.getDatasetVersion(datasetPid, "1.0", apiToken);
Response v1 = UtilIT.getDatasetVersion(datasetPid, "1.0", false, apiToken);
v1.prettyPrint();
v1.then().assertThat()
.body("data.files[0].dataFile.filename", equalTo("cc0.png"))
Expand All @@ -2011,7 +2011,7 @@ public void testDeleteFile() {
downloadResponse2.then().assertThat().statusCode(OK.getStatusCode());

// Check file 3 still in post v1.0 draft
Response postv1draft2 = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, apiToken);
Response postv1draft2 = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, false, apiToken);
postv1draft2.prettyPrint();
postv1draft2.then().assertThat()
.body("data.files[0].dataFile.filename", equalTo("orcid_16x16.png"))
Expand All @@ -2026,7 +2026,7 @@ public void testDeleteFile() {
deleteResponse3.then().assertThat().statusCode(OK.getStatusCode());

// Check file 3 deleted from post v1.0 draft
Response postv1draft3 = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, apiToken);
Response postv1draft3 = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, false, apiToken);
postv1draft3.prettyPrint();
postv1draft3.then().assertThat()
.body("data.files[0]", equalTo(null))
Expand Down
9 changes: 8 additions & 1 deletion src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -1399,9 +1399,10 @@ static Response nativeGetUsingPersistentId(String persistentId, String apiToken)
return response;
}

static Response getDatasetVersion(String persistentId, String versionNumber, String apiToken) {
static Response getDatasetVersion(String persistentId, String versionNumber, boolean includeDeaccessioned, String apiToken) {
return given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
.queryParam("includeDeaccessioned", includeDeaccessioned)
.get("/api/datasets/:persistentId/versions/" + versionNumber + "?persistentId=" + persistentId);
}

Expand Down Expand Up @@ -3359,6 +3360,12 @@ static Response getUserPermissionsOnFile(String dataFileId, String apiToken) {
.get("/api/access/datafile/" + dataFileId + "/userPermissions");
}

static Response getUserPermissionsOnDataset(String datasetId, String apiToken) {
return given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
.get("/api/datasets/" + datasetId + "/userPermissions");
}

static Response createFileEmbargo(Integer datasetId, Integer fileId, String dateAvailable, String apiToken) {
JsonObjectBuilder jsonBuilder = Json.createObjectBuilder();
jsonBuilder.add("dateAvailable", dateAvailable);
Expand Down