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

6290 dataset role mgmt api #6622

Merged
merged 24 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ba9b90e
#6290 add DS Role api and tests
sekmiller Feb 5, 2020
7d7a288
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 5, 2020
dac1ad1
#6290 cannot add role without permission
sekmiller Feb 6, 2020
3c24610
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 6, 2020
6286fc1
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 6, 2020
cd120ee
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 6, 2020
ba7f66c
#6290 Add doc to dataset role api
sekmiller Feb 6, 2020
039bd5e
#6920 add revoke role api for datasets
sekmiller Feb 10, 2020
1b191a4
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 10, 2020
9eb308a
#6290 bundle-ize api messages
sekmiller Feb 10, 2020
f341af2
#6290 Add doc for revoke role on Dataset
sekmiller Feb 10, 2020
fed7f86
#6290 fix doc typo
sekmiller Feb 10, 2020
7d60cc6
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 11, 2020
71e3e03
#6290 fix list dataset roles after merge conflict
sekmiller Feb 11, 2020
a064189
#6290 add code block tags
sekmiller Feb 11, 2020
d1dafdc
reformat
sekmiller Feb 11, 2020
4e3de66
once again
sekmiller Feb 11, 2020
9a1ff01
Update native-api.rst
sekmiller Feb 11, 2020
0a7d066
Update native-api.rst
sekmiller Feb 11, 2020
6098888
Update native-api.rst
sekmiller Feb 12, 2020
7288f91
Update native-api.rst
sekmiller Feb 12, 2020
9e31154
#6290 get role assignments at DV level for DS
sekmiller Feb 13, 2020
b7c6050
Merge branch 'develop' into 6290-dataset-role-mgmt-api
sekmiller Feb 13, 2020
b7b3645
Update native-api.rst
sekmiller Feb 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,13 @@ Assigns a new role, based on the POSTed JSON:
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H X-Dataverse-key:$API_TOKEN -X POST $SERVER_URL/api/dataverses/$ID/assignments --upload-file role.json
curl -H X-Dataverse-key:$API_TOKEN -X POST -H "Content-Type: application/json" $SERVER_URL/api/dataverses/$ID/assignments --upload-file role.json

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X POST https://demo.dataverse.org/api/dataverses/root/assignments --upload-file role.json
curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X POST -H "Content-Type: application/json" https://demo.dataverse.org/api/dataverses/root/assignments --upload-file role.json

POSTed JSON example (the content of ``role.json`` file)::

Expand Down Expand Up @@ -1018,24 +1018,77 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE https://demo.dataverse.org/api/datasets/24/citationdate

List Role Assignments for a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. _list-roles-on-a-dataset-api:

List Role Assignments in a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List all the role assignments at the given dataset:
Lists all role assignments on a given dataset:

.. code-block:: bash

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

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

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" https://demo.dataverse.org/api/datasets/24/assignments
curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx https://demo.dataverse.org/api/datasets/2347/assignments

.. _assign-role-on-a-dataset-api:

Assign a New Role on a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Assigns a new role, based on the POSTed JSON:

.. code-block:: bash

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

curl -H X-Dataverse-key:$API_TOKEN -X POST -H "Content-Type: application/json" $SERVER_URL/api/datasets/$ID/assignments --upload-file role.json

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X POST -H "Content-Type: application/json" https://demo.dataverse.org/api/datasets/2347/assignments --upload-file role.json

POSTed JSON example (the content of ``role.json`` file)::

{
"assignee": "@uma",
"role": "curator"
}

.. _revoke-role-on-a-dataset-api:

Delete Role Assignment from a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Delete the assignment whose id is ``$id``:

.. code-block:: bash

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

curl -H X-Dataverse-key:$API_TOKEN -X DELETE $SERVER_URL/api/datasets/$ID/assignments/$ASSIGNMENT_ID

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X DELETE https://demo.dataverse.org/api/datasets/2347/assignments/6


Create a Private URL for a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
65 changes: 53 additions & 12 deletions src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.DataverseSession;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.EjbDataverseEngine;
import edu.harvard.iq.dataverse.MetadataBlock;
import edu.harvard.iq.dataverse.MetadataBlockServiceBean;
import edu.harvard.iq.dataverse.PermissionServiceBean;
import edu.harvard.iq.dataverse.RoleAssignment;
import edu.harvard.iq.dataverse.UserNotification;
import edu.harvard.iq.dataverse.UserNotificationServiceBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
Expand Down Expand Up @@ -77,10 +79,12 @@
import edu.harvard.iq.dataverse.privateurl.PrivateUrl;
import edu.harvard.iq.dataverse.S3PackageImporter;
import static edu.harvard.iq.dataverse.api.AbstractApiBean.error;
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
import edu.harvard.iq.dataverse.batch.util.LoggingUtil;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import edu.harvard.iq.dataverse.engine.command.exception.UnforcedCommandException;
import edu.harvard.iq.dataverse.engine.command.impl.GetDatasetStorageSizeCommand;
import edu.harvard.iq.dataverse.engine.command.impl.RevokeRoleCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDvObjectPIDMetadataCommand;
import edu.harvard.iq.dataverse.makedatacount.DatasetExternalCitations;
import edu.harvard.iq.dataverse.makedatacount.DatasetExternalCitationsServiceBean;
Expand Down Expand Up @@ -137,6 +141,7 @@
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import javax.ws.rs.core.UriInfo;
import org.apache.solr.client.solrj.SolrServerException;
Expand Down Expand Up @@ -1084,31 +1089,67 @@ public Response getLinks(@PathParam("id") String idSupplied ) {
}

/**
* @todo Make this real. Currently only used for API testing. Copied from
* the equivalent API endpoint for dataverses and simplified with values
* hard coded.
* Add a given assignment to a given user or group
* @param ra role assignment DTO
* @param id dataset id
* @param apiKey
*/
@POST
@Path("{identifier}/assignments")
public Response createAssignment(String userOrGroup, @PathParam("identifier") String id, @QueryParam("key") String apiKey) {
boolean apiTestingOnly = true;
if (apiTestingOnly) {
return error(Response.Status.FORBIDDEN, "This is only for API tests.");
}
public Response createAssignment(RoleAssignmentDTO ra, @PathParam("identifier") String id, @QueryParam("key") String apiKey) {
try {
Dataset dataset = findDatasetOrDie(id);
RoleAssignee assignee = findAssignee(userOrGroup);

RoleAssignee assignee = findAssignee(ra.getAssignee());
if (assignee == null) {
return error(Response.Status.BAD_REQUEST, "Assignee not found");
return error(Response.Status.BAD_REQUEST, BundleUtil.getStringFromBundle("datasets.api.grant.role.assignee.not.found.error"));
}

DataverseRole theRole;
Dataverse dv = dataset.getOwner();
theRole = null;
while ((theRole == null) && (dv != null)) {
for (DataverseRole aRole : rolesSvc.availableRoles(dv.getId())) {
if (aRole.getAlias().equals(ra.getRole())) {
theRole = aRole;
break;
}
}
dv = dv.getOwner();
}
DataverseRole theRole = rolesSvc.findBuiltinRoleByAlias("admin");
if (theRole == null) {
List<String> args = Arrays.asList(ra.getRole(), dataset.getOwner().getDisplayName());
return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("datasets.api.grant.role.not.found.error", args));
}

String privateUrlToken = null;
return ok(
json(execCommand(new AssignRoleCommand(assignee, theRole, dataset, createDataverseRequest(findUserOrDie()), privateUrlToken))));
} catch (WrappedResponse ex) {
logger.log(Level.WARNING, "Can''t create assignment: {0}", ex.getMessage());
List<String> args = Arrays.asList(ex.getMessage());
logger.log(Level.WARNING, BundleUtil.getStringFromBundle("datasets.api.grant.role.cant.create.assignment.error", args));
return ex.getResponse();
}

}

@DELETE
@Path("{identifier}/assignments/{id}")
public Response deleteAssignment(@PathParam("id") long assignmentId, @PathParam("identifier") String dsId) {
RoleAssignment ra = em.find(RoleAssignment.class, assignmentId);
if (ra != null) {
try {
findDatasetOrDie(dsId);
execCommand(new RevokeRoleCommand(ra, createDataverseRequest(findUserOrDie())));
List<String> args = Arrays.asList(ra.getRole().getName(), ra.getAssigneeIdentifier(), ra.getDefinitionPoint().accept(DvObject.NamePrinter));
return ok(BundleUtil.getStringFromBundle("datasets.api.revoke.role.success", args));
} catch (WrappedResponse ex) {
return ex.getResponse();
}
} else {
List<String> args = Arrays.asList(Long.toString(assignmentId));
return error(Status.NOT_FOUND, BundleUtil.getStringFromBundle("datasets.api.revoke.role.not.found.error", args));
}
}

@GET
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import java.util.ArrayList;
import java.util.List;

/**
Expand All @@ -25,6 +26,12 @@ public ListRoleAssignments(DataverseRequest aRequest, DvObject aDefinitionPoint)

@Override
public List<RoleAssignment> execute(CommandContext ctxt) throws CommandException {
if(definitionPoint.isInstanceofDataset()){
List <RoleAssignment> retVal = new ArrayList();
retVal.addAll(ctxt.permissions().assignmentsOn(definitionPoint));
retVal.addAll(ctxt.permissions().assignmentsOn(definitionPoint.getOwner()));
return retVal;
}
return ctxt.permissions().assignmentsOn(definitionPoint);
}

Expand Down
6 changes: 5 additions & 1 deletion src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2229,7 +2229,11 @@ datasets.api.listing.error=Fatal error trying to list the contents of the datase
datasets.api.datasize.storage=Total size of the files stored in this dataset: {0} bytes
datasets.api.datasize.download=Total size of the files available for download in this version of the dataset: {0} bytes
datasets.api.datasize.ioerror=Fatal IO error while trying to determine the total size of the files stored in the dataset. Please report this error to the Dataverse administrator.

datasets.api.grant.role.not.found.error=Cannot find role named ''{0}'' in dataverse {1}
datasets.api.grant.role.cant.create.assignment.error=Cannot create assignment: {0}
datasets.api.grant.role.assignee.not.found.error=Assignee not found
datasets.api.revoke.role.not.found.error="Role assignment {0} not found"
datasets.api.revoke.role.success=Role {0} revoked for assignee {1} in {2}

#Dataverses.java
dataverses.api.update.default.contributor.role.failure.role.not.found=Role {0} not found.
Expand Down
86 changes: 86 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static com.jayway.restassured.path.json.JsonPath.with;
import com.jayway.restassured.path.xml.XmlPath;
import static edu.harvard.iq.dataverse.api.UtilIT.equalToCI;
import static edu.harvard.iq.dataverse.authorization.AuthenticationResponse.Status.ERROR;
import edu.harvard.iq.dataverse.authorization.groups.impl.builtin.AuthenticatedUsers;
import edu.harvard.iq.dataverse.util.SystemConfig;
import java.io.IOException;
Expand All @@ -46,6 +47,7 @@
import org.junit.AfterClass;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.containsString;

public class DatasetsIT {

Expand Down Expand Up @@ -1101,6 +1103,90 @@ public void testPrivateUrl() {
* @todo Should the Search API work with the Private URL token?
*/
}

@Test
public void testAddRoles(){

Response createUser = UtilIT.createRandomUser();
createUser.prettyPrint();
String username = UtilIT.getUsernameFromResponse(createUser);
String apiToken = UtilIT.getApiTokenFromResponse(createUser);

Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
createDataverseResponse.prettyPrint();
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);

Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
createDatasetResponse.prettyPrint();
Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse);

Response datasetAsJson = UtilIT.nativeGet(datasetId, apiToken);
datasetAsJson.then().assertThat()
.statusCode(OK.getStatusCode());

String identifier = JsonPath.from(datasetAsJson.getBody().asString()).getString("data.identifier");
assertEquals(10, identifier.length());

String protocol1 = JsonPath.from(datasetAsJson.getBody().asString()).getString("data.protocol");
String authority1 = JsonPath.from(datasetAsJson.getBody().asString()).getString("data.authority");
String identifier1 = JsonPath.from(datasetAsJson.getBody().asString()).getString("data.identifier");
String datasetPersistentId = protocol1 + ":" + authority1 + "/" + identifier1;



// Create another random user:

Response createRandomUser = UtilIT.createRandomUser();
createRandomUser.prettyPrint();
String randomUsername = UtilIT.getUsernameFromResponse(createRandomUser);
String randomUserApiToken = UtilIT.getApiTokenFromResponse(createRandomUser);


//Give that random user permission
//(String definitionPoint, String role, String roleAssignee, String apiToken)
//Can't give yourself permission
Response giveRandoPermission = UtilIT.grantRoleOnDataset(datasetPersistentId, "fileDownloader", "@" + randomUsername, randomUserApiToken);
giveRandoPermission.prettyPrint();
assertEquals(401, giveRandoPermission.getStatusCode());

giveRandoPermission = UtilIT.grantRoleOnDataset(datasetPersistentId, "fileDownloader", "@" + randomUsername, apiToken);
giveRandoPermission.prettyPrint();
assertEquals(200, giveRandoPermission.getStatusCode());

String idToDelete = JsonPath.from(giveRandoPermission.getBody().asString()).getString("data.id");

giveRandoPermission = UtilIT.grantRoleOnDataset(datasetPersistentId, "designatedHitter", "@" + randomUsername, apiToken);
giveRandoPermission.prettyPrint();
giveRandoPermission.then().assertThat()
.contentType(ContentType.JSON)
.body("message", containsString("Cannot find role named 'designatedHitter' in dataverse "))
.statusCode(400);
assertEquals(400, giveRandoPermission.getStatusCode());

//Try to delete Role with Id saved above
//Fails for lack of perms
Response deleteGrantedAccess = UtilIT.revokeRoleOnDataset(datasetPersistentId, new Long(idToDelete), randomUserApiToken);
deleteGrantedAccess.prettyPrint();
assertEquals(401, deleteGrantedAccess.getStatusCode());

//Should be able to delete with proper apiToken
deleteGrantedAccess = UtilIT.revokeRoleOnDataset(datasetPersistentId, new Long(idToDelete), apiToken);
deleteGrantedAccess.prettyPrint();
assertEquals(200, deleteGrantedAccess.getStatusCode());

Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, apiToken);
deleteDatasetResponse.prettyPrint();
assertEquals(200, deleteDatasetResponse.getStatusCode());

Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, apiToken);
deleteDataverseResponse.prettyPrint();
assertEquals(200, deleteDataverseResponse.getStatusCode());

Response deleteUserResponse = UtilIT.deleteUser(username);
deleteUserResponse.prettyPrint();
assertEquals(200, deleteUserResponse.getStatusCode());

}

@Test
public void testFileChecksum() {
Expand Down
18 changes: 16 additions & 2 deletions src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -1587,10 +1587,17 @@ static Response getRoleAssignmentsOnDataset(String datasetId, String persistentI
}

static Response grantRoleOnDataset(String definitionPoint, String role, String roleAssignee, String apiToken) {

JsonObjectBuilder roleBuilder = Json.createObjectBuilder();
roleBuilder.add("assignee", roleAssignee);
roleBuilder.add("role", role);

JsonObject roleObject = roleBuilder.build();
logger.info("Granting role on dataset \"" + definitionPoint + "\": " + role);
return given()
.body("@" + roleAssignee)
.post("api/datasets/" + definitionPoint + "/assignments?key=" + apiToken);
.body(roleObject.toString())
.contentType(ContentType.JSON)
.post("api/datasets/:persistentId/assignments?key=" + apiToken + "&persistentId=" + definitionPoint);
}

static Response revokeRole(String definitionPoint, long doomed, String apiToken) {
Expand All @@ -1599,6 +1606,13 @@ static Response revokeRole(String definitionPoint, long doomed, String apiToken)
.delete("api/dataverses/" + definitionPoint + "/assignments/" + doomed);
}

static Response revokeRoleOnDataset(String definitionPoint, long doomed, String apiToken) {

return given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
.delete("api/datasets/:persistentId/assignments/" + doomed + "?persistentId=" + definitionPoint);
}

static Response revokeFileAccess(String definitionPoint, String doomed, String apiToken) {
return given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
Expand Down