Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions doc/release-notes/11534-link-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Linking or unlinking a dataset or dataverse now requires the new "Link Dataset/Dataverse" permission.
Previously, this action was covered by the "Publish Dataset/Dataverse" permission.
Linking and publishing permissions can now be granted separately, allowing for more fine-grained access control.
4 changes: 2 additions & 2 deletions doc/sphinx-guides/source/admin/dataverses-datasets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Moves a dataset whose id is passed to a Dataverse collection whose alias is pass
Link a Dataset
^^^^^^^^^^^^^^

Creates a link between a dataset and a Dataverse collection (see the :ref:`dataset-linking` section of the User Guide for more information). ::
Creates a link between a dataset and a Dataverse collection (see the :ref:`dataset-linking` section of the User Guide for more information). Accessible to users with Link Dataset permission on the Dataverse collection. ::

curl -H "X-Dataverse-key: $API_TOKEN" -X PUT http://$SERVER/api/datasets/$linked-dataset-id/link/$linking-dataverse-alias

Expand Down Expand Up @@ -155,7 +155,7 @@ It returns a list in the following format (new format as of v6.4):
Unlink a Dataset
^^^^^^^^^^^^^^^^

Removes a link between a dataset and a Dataverse collection. Accessible to users with Publish Dataset permissions. ::
Removes a link between a dataset and a Dataverse collection. Accessible to users with Link Dataset permission on the Dataverse collection. ::

curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE http://$SERVER/api/datasets/$linked-dataset-id/deleteLink/$linking-dataverse-alias

Expand Down
2 changes: 1 addition & 1 deletion doc/sphinx-guides/source/user/dataverse-management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ Dataset linking allows a Dataverse collection owner to "link" their Dataverse co

For example, researchers working on a collaborative study across institutions can each link their own individual institutional Dataverse collections to the one collaborative dataset, making it easier for interested parties from each institution to find the study.

In order to link a dataset, you will need your account to have the "Publish Dataset" permission on the Dataverse collection that is doing the linking. If you created the Dataverse collection then you should have this permission already, but if not then you will need to ask the admin of that Dataverse collection to assign that permission to your account. You do not need any special permissions on the dataset being linked.
In order to link a dataset, you will need your account to have the "Link Dataset" permission on the Dataverse collection that is doing the linking. If you created the Dataverse collection then you should have this permission already, but if not then you will need to ask the admin of that Dataverse collection to assign that permission to your account. You do not need any special permissions on the dataset being linked.

To link a dataset to your Dataverse collection, you must navigate to that dataset and click the white "Link" button in the upper-right corner of the dataset page. This will open up a window where you can type in the name of the Dataverse collection that you would like to link the dataset to. Select your Dataverse collection and click the save button. This will establish the link, and the dataset will now appear under your Dataverse collection.

Expand Down
3 changes: 2 additions & 1 deletion scripts/api/data/role-curator.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"alias":"curator",
"name":"Curator",
"description":"For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.",
"description":"For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.",
"permissions":[
"ViewUnpublishedDataset",
"EditDataset",
"DownloadFile",
"DeleteDatasetDraft",
"PublishDataset",
"LinkDataset",
"ManageDatasetPermissions",
"ManageFilePermissions",
"AddDataverse",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ public List<Dataverse> filterDataversesForLinking(String query, DataverseRequest

for (Dataverse res : results) {
if (!remove.contains(res)) {
if (this.permissionService.requestOn(req, res).has(Permission.PublishDataset)) {
if (this.permissionService.requestOn(req, res).has(Permission.LinkDataset)) {
dataverseList.add(res);
}
}
Expand All @@ -525,7 +525,7 @@ public List<Dataverse> filterDataversesForUnLinking(String query, DataverseReque
List<Dataverse> dataverseList = new ArrayList<>();
if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) {
alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((dataverse) -> {
if (this.permissionService.requestOn(req, dataverse).has(Permission.PublishDataset)) {
if (this.permissionService.requestOn(req, dataverse).has(Permission.LinkDataset)) {
dataverseList.add(dataverse);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ public enum Permission implements java.io.Serializable {
ManageDatasetPermissions(BundleUtil.getStringFromBundle("permission.managePermissionsDataset"), true, Dataset.class),
ManageFilePermissions(BundleUtil.getStringFromBundle("permission.managePermissionsDataFile"), true, DataFile.class),
PublishDataverse(BundleUtil.getStringFromBundle("permission.publishDataverse"), true, Dataverse.class),
LinkDataverse(BundleUtil.getStringFromBundle("permission.linkDataverse"), true, Dataverse.class),
PublishDataset(BundleUtil.getStringFromBundle("permission.publishDataset"), true, Dataset.class, Dataverse.class),
LinkDataset(BundleUtil.getStringFromBundle("permission.linkDataset"), true, Dataset.class, Dataverse.class),
// Delete
DeleteDataverse(BundleUtil.getStringFromBundle("permission.deleteDataverse"), true, Dataverse.class),
DeleteDatasetDraft(BundleUtil.getStringFromBundle("permission.deleteDataset"), true, Dataset.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
* @author sarahferry
*/

@RequiredPermissions( Permission.PublishDataset )
@RequiredPermissions( Permission.LinkDataset )
public class DeleteDatasetLinkingDataverseCommand extends AbstractCommand<Dataset>{
private final DatasetLinkingDataverse doomed;
private final Dataset editedDs;
private final boolean index;

public DeleteDatasetLinkingDataverseCommand(DataverseRequest aRequest, Dataset editedDs , DatasetLinkingDataverse doomed, boolean index) {
super(aRequest, editedDs);
super(aRequest, doomed.getLinkingDataverse());
this.editedDs = editedDs;
this.doomed = doomed;
this.index = index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*
* @author skraffmiller
*/
@RequiredPermissions(Permission.PublishDataset)
@RequiredPermissions(Permission.LinkDataset)
public class LinkDatasetCommand extends AbstractCommand<DatasetLinkingDataverse> {

private final Dataset linkedDataset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
*
* @author skraffmiller
*/
@RequiredPermissions(Permission.PublishDataverse)
@RequiredPermissions(Permission.LinkDataverse)
public class LinkDataverseCommand extends AbstractCommand<DataverseLinkingDataverse> {

private final Dataverse linkedDataverse;
Expand All @@ -47,7 +47,7 @@ public LinkDataverseCommand(DataverseRequest aRequest, Dataverse dataverse, Data
public DataverseLinkingDataverse execute(CommandContext ctxt) throws CommandException {
if ((!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser())) {
throw new PermissionException("Link Dataverse can only be called by superusers.",
this, Collections.singleton(Permission.PublishDataverse), linkingDataverse);
this, Collections.singleton(Permission.LinkDataverse), linkingDataverse);
}
if (linkedDataverse.equals(linkingDataverse)) {
throw new IllegalCommandException("Can't link a dataverse to itself", this);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/propertyFiles/BuiltInRoles.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ role.admin.description=A person who has all permissions for dataverses, datasets
role.contributor.name=Contributor
role.contributor.description=For datasets, a person who can edit License + Terms, and then submit them for review.
role.curator.name=Curator
role.curator.description=For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.
role.curator.description=For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.
role.dscontributor.name=Dataset Creator
role.dscontributor.description=A person who can add datasets within a dataverse.
role.fullcontributor.name=Dataverse + Dataset Creator
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2555,7 +2555,9 @@ permission.addDataverseDataverse=Add a dataverse within another dataverse
permission.deleteDataset=Delete a dataset draft
permission.deleteDataverse=Delete an unpublished dataverse
permission.publishDataset=Publish a dataset
permission.linkDataset=Link a dataset to a dataverse
permission.publishDataverse=Publish a dataverse
permission.linkDataverse=Link a dataverse to another dataverse
permission.managePermissionsDataFile=Manage permissions for a file
permission.managePermissionsDataset=Manage permissions for a dataset
permission.managePermissionsDataverse=Manage permissions for a dataverse
Expand Down Expand Up @@ -2907,7 +2909,9 @@ permission.EditDataset.label=EditDataset
permission.ManageDataversePermissions.label=ManageDataversePermissions
permission.ManageDatasetPermissions.label=ManageDatasetPermissions
permission.PublishDataverse.label=PublishDataverse
permission.LinkDataverse.label=LinkDataverse
permission.PublishDataset.label=PublishDataset
permission.LinkDataset.label=LinkDataset
permission.DeleteDataverse.label=DeleteDataverse
permission.DeleteDatasetDraft.label=DeleteDatasetDraft
permission.ManageFilePermissions.label=ManageFilePermissions
Expand Down
38 changes: 23 additions & 15 deletions src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -2792,13 +2792,19 @@ public void testDcmChecksumValidationMessages() throws IOException, InterruptedE

@Test
public void testCreateDeleteDatasetLink() {
// Create superuser
Response createUser = UtilIT.createRandomUser();
createUser.prettyPrint();
String username = UtilIT.getUsernameFromResponse(createUser);
String apiToken = UtilIT.getApiTokenFromResponse(createUser);

Response superuserResponse = UtilIT.makeSuperUser(username);

// Create another user that doesn't have permission to create/delete links
Response createUser2 = UtilIT.createRandomUser();
createUser2.prettyPrint();
String username2 = UtilIT.getUsernameFromResponse(createUser2);
String apiToken2 = UtilIT.getApiTokenFromResponse(createUser2);

Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
createDataverseResponse.prettyPrint();
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
Expand Down Expand Up @@ -2834,28 +2840,31 @@ public void testCreateDeleteDatasetLink() {
publishDatasetForLinking.prettyPrint();
publishTargetDataverse.then().assertThat()
.statusCode(OK.getStatusCode());

// And link the dataset to this new dataverse:

// Try to link the dataset to the new dataverse without LinkDataset permissions
createLinkingDatasetResponse = UtilIT.createDatasetLink(datasetId.longValue(), dataverseAlias, apiToken2);
createLinkingDatasetResponse.prettyPrint();
createLinkingDatasetResponse.then().assertThat()
.body("message", equalTo("User @" + username2 + " is not permitted to perform requested action."))
.statusCode(UNAUTHORIZED.getStatusCode());

// Link the dataset to the new dataverse
createLinkingDatasetResponse = UtilIT.createDatasetLink(datasetId.longValue(), dataverseAlias, apiToken);
createLinkingDatasetResponse.prettyPrint();
createLinkingDatasetResponse.then().assertThat()
.body("data.message", equalTo("Dataset " + datasetId +" linked successfully to " + dataverseAlias))
.statusCode(200);

// Create a new user that doesn't have permission to delete the link
Response createUser2 = UtilIT.createRandomUser();
createUser2.prettyPrint();
String username2 = UtilIT.getUsernameFromResponse(createUser2);
String apiToken2 = UtilIT.getApiTokenFromResponse(createUser2);
// Try to delete the link without PublishDataset permissions
// Try to delete the link without LinkDataset permissions
Response deleteLinkingDatasetResponse = UtilIT.deleteDatasetLink(datasetId.longValue(), dataverseAlias, apiToken2);
deleteLinkingDatasetResponse.prettyPrint();
deleteLinkingDatasetResponse.then().assertThat()
.body("message", equalTo("User @" + username2 + " is not permitted to perform requested action."))
.statusCode(UNAUTHORIZED.getStatusCode());

// Add the Curator role to this user to show that they can delete the link later. (Timing issues if you try to delete right after giving permission)
Response givePermissionResponse = UtilIT.grantRoleOnDataset(datasetPersistentId, "curator", "@" + username2, apiToken);
// Give the user curator rights for the target dataverse to show that they can add and delete the link later
// (Timing issues if you try to add or delete right after giving permission)
Response givePermissionResponse = UtilIT.grantRoleOnDataverse(dataverseAlias, "curator", "@" + username2, apiToken);
givePermissionResponse.prettyPrint();
givePermissionResponse.then().assertThat()
.statusCode(200);
Expand All @@ -2868,17 +2877,16 @@ public void testCreateDeleteDatasetLink() {
.body("data.message", equalTo("Link from Dataset " + datasetId + " to linked Dataverse " + dataverseAlias + " deleted"))
.statusCode(200);

// And re-link the dataset to this new dataverse:
createLinkingDatasetResponse = UtilIT.createDatasetLink(datasetId.longValue(), dataverseAlias, apiToken);
// And now test linking the dataset as user2 with new role as curator (link permissions):
createLinkingDatasetResponse = UtilIT.createDatasetLink(datasetId.longValue(), dataverseAlias, apiToken2);
createLinkingDatasetResponse.prettyPrint();
createLinkingDatasetResponse.then().assertThat()
.body("data.message", equalTo("Dataset " + datasetId +" linked successfully to " + dataverseAlias))
.statusCode(200);

// And now test deleting it as user2 with new role as curator (Publish permissions):
// And now test deleting it as user2 with new role as curator (link permissions):
deleteLinkingDatasetResponse = UtilIT.deleteDatasetLink(datasetId.longValue(), dataverseAlias, apiToken2);
deleteLinkingDatasetResponse.prettyPrint();

deleteLinkingDatasetResponse.then().assertThat()
.body("data.message", equalTo("Link from Dataset " + datasetId + " to linked Dataverse " + dataverseAlias + " deleted"))
.statusCode(200);
Expand Down