diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index f3b72b5c3af..4a1653b9a9c 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -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):: @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 388d22a8b29..bf54b9bd696 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -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; @@ -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; @@ -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; @@ -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 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 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 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 args = Arrays.asList(Long.toString(assignmentId)); + return error(Status.NOT_FOUND, BundleUtil.getStringFromBundle("datasets.api.revoke.role.not.found.error", args)); + } } @GET diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListRoleAssignments.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListRoleAssignments.java index ed438bc3815..1858ba377ab 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListRoleAssignments.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListRoleAssignments.java @@ -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; /** @@ -25,6 +26,12 @@ public ListRoleAssignments(DataverseRequest aRequest, DvObject aDefinitionPoint) @Override public List execute(CommandContext ctxt) throws CommandException { + if(definitionPoint.isInstanceofDataset()){ + List retVal = new ArrayList(); + retVal.addAll(ctxt.permissions().assignmentsOn(definitionPoint)); + retVal.addAll(ctxt.permissions().assignmentsOn(definitionPoint.getOwner())); + return retVal; + } return ctxt.permissions().assignmentsOn(definitionPoint); } diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 04b19bb59ee..1d54a749d9a 100755 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -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. 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 666f006ce3e..51d3a214d5d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -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; @@ -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 { @@ -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() { 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 a744bcd0ba5..c609b400f7e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -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) { @@ -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)