Skip to content

Commit 16ea4cc

Browse files
authored
Merge pull request #12250 from vera/feat/non-superuser-dataverse-linking
Dataverse collection linking/unlinking without superuser status
2 parents 0df5da7 + 9af0c8c commit 16ea4cc

14 files changed

Lines changed: 187 additions & 106 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Dataverse collection linking and unlinking no longer requires superuser status. Users with the "Link Dataverse" permission on a collection can now perform these actions through the UI and API.

doc/sphinx-guides/source/admin/dataverses-datasets.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ Moves a Dataverse collection whose id is passed to an existing Dataverse collect
2222
Link a Dataverse Collection
2323
^^^^^^^^^^^^^^^^^^^^^^^^^^^
2424

25-
Creates a link between a Dataverse collection and another Dataverse collection (see the :ref:`dataverse-linking` section of the User Guide for more information). Only accessible to superusers. ::
25+
Creates a link between a Dataverse collection and another Dataverse collection (see the :ref:`dataverse-linking` section of the User Guide for more information). ::
2626

2727
curl -H "X-Dataverse-key: $API_TOKEN" -X PUT http://$SERVER/api/dataverses/$linked-dataverse-alias/link/$linking-dataverse-alias
2828

2929
Unlink a Dataverse Collection
3030
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3131

32-
Removes a link between a Dataverse collection and another Dataverse collection. Only accessible to superusers. ::
32+
Removes a link between a Dataverse collection and another Dataverse collection. Accessible to users with Link Dataverse permission on the linking Dataverse collection. ::
3333

3434
curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE http://$SERVER/api/dataverses/$linked-dataverse-alias/deleteLink/$linking-dataverse-alias
3535

doc/sphinx-guides/source/api/native-api.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3746,6 +3746,66 @@ The fully expanded example above (without environment variables) looks like this
37463746
37473747
curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X PUT "https://demo.dataverse.org/api/datasets/24/link/test"
37483748
3749+
Unlink a Dataset
3750+
~~~~~~~~~~~~~~~~
3751+
3752+
Removes a link between a dataset and a Dataverse collection (see :ref:`dataset-linking` section of Dataverse Collection Management in the User Guide for more information):
3753+
3754+
.. code-block:: bash
3755+
3756+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3757+
export SERVER_URL=https://demo.dataverse.org
3758+
export DATASET_ID=24
3759+
export DATAVERSE_ID=test
3760+
3761+
curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/datasets/$DATASET_ID/deleteLink/$DATAVERSE_ID"
3762+
3763+
The fully expanded example above (without environment variables) looks like this:
3764+
3765+
.. code-block:: bash
3766+
3767+
curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/datasets/24/deleteLink/test"
3768+
3769+
Link a Dataverse collection
3770+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
3771+
3772+
Creates a link between one Dataverse collection and another Dataverse collection (see :ref:`dataverse-linking` section of Dataverse Collection Management in the User Guide for more information):
3773+
3774+
.. code-block:: bash
3775+
3776+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3777+
export SERVER_URL=https://demo.dataverse.org
3778+
export LINKED_DATAVERSE_ID=linked-collection
3779+
export LINKING_DATAVERSE_ID=linking-collection
3780+
3781+
curl -H "X-Dataverse-key: $API_TOKEN" -X PUT "$SERVER_URL/api/dataverses/$LINKED_DATAVERSE_ID/link/$LINKING_DATAVERSE_ID"
3782+
3783+
The fully expanded example above (without environment variables) looks like this:
3784+
3785+
.. code-block:: bash
3786+
3787+
curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X PUT "https://demo.dataverse.org/api/dataverses/linked-collection/link/linking-collection"
3788+
3789+
Unlink a Dataverse collection
3790+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3791+
3792+
Removes a link between one Dataverse collection and another Dataverse collection (see :ref:`dataverse-linking` section of Dataverse Collection Management in the User Guide for more information):
3793+
3794+
.. code-block:: bash
3795+
3796+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3797+
export SERVER_URL=https://demo.dataverse.org
3798+
export LINKED_DATAVERSE_ID=linked-collection
3799+
export LINKING_DATAVERSE_ID=linking-collection
3800+
3801+
curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/dataverses/$LINKED_DATAVERSE_ID/deleteLink/$LINKING_DATAVERSE_ID"
3802+
3803+
The fully expanded example above (without environment variables) looks like this:
3804+
3805+
.. code-block:: bash
3806+
3807+
curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/dataverses/linked-collection/deleteLink/linking-collection"
3808+
37493809
Dataset Locks
37503810
~~~~~~~~~~~~~
37513811

doc/sphinx-guides/source/user/dataverse-management.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,18 +221,18 @@ In order to link a dataset, you will need your account to have the "Link Dataset
221221

222222
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.
223223

224-
A draft dataset can be linked to other Dataverse collections. It will only become publicly visible in the linked collection(s) after it has been published. To publish the dataset, your account must have the "Publish Dataset" permission for the Dataverse collection in which the dataset was originally created. Permissions in the linked Dataverse collections do not apply.
224+
To remove an established link, navigate to the linked dataset's page and click the white "Unlink" button in the upper-right corner of the page.
225225

226-
There is currently no way to remove established links in the UI. If you need to remove a link between a Dataverse collection and a dataset, please contact the support team for the Dataverse installation you are using (see the :ref:`unlink-a-dataset` section of the Admin Guide for more information).
226+
A draft dataset can be linked to other Dataverse collections. It will only become publicly visible in the linked collection(s) after it has been published. To publish the dataset, your account must have the "Publish Dataset" permission for the Dataverse collection in which the dataset was originally created. Permissions in the linked Dataverse collections do not apply.
227227

228228
.. _dataverse-linking:
229229

230230
Dataverse Collection Linking
231231
============================
232232

233-
Similarly to dataset linking, Dataverse collection linking allows a Dataverse collection owner to "link" their Dataverse collection to another Dataverse collection, so the Dataverse collection being linked will appear in the linking Dataverse collection's list of contents without actually *being* in that Dataverse collection. Currently, the ability to link a Dataverse collection to another Dataverse collection is a superuser only feature.
233+
Similarly to dataset linking, Dataverse collection linking allows a Dataverse collection owner to "link" their Dataverse collection to another Dataverse collection, so the Dataverse collection being linked will appear in the linking Dataverse collection's list of contents without actually *being* in that Dataverse collection.
234234

235-
If you need to have a Dataverse collection linked to your Dataverse collection, please contact the support team for the Dataverse installation you are using.
235+
In order to link a collection, you will need your account to have the "Link Dataverse" permission on the linking Dataverse collection.
236236

237237
Publish Your Dataverse Collection
238238
=================================

src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,19 @@ public void save(DataverseLinkingDataverse dataverseLinkingDataverse) {
8989
}
9090
}
9191

92-
public DataverseLinkingDataverse findDataverseLinkingDataverse(Long dataverseId, Long linkingDataverseId) {
92+
public DataverseLinkingDataverse findDataverseLinkingDataverse(Long linkingDataverseId, Long linkedDataverseId) {
9393
try {
9494
return em.createNamedQuery("DataverseLinkingDataverse.findByDataverseIdAndLinkingDataverseId", DataverseLinkingDataverse.class)
95-
.setParameter("dataverseId", dataverseId)
95+
.setParameter("dataverseId", linkedDataverseId)
9696
.setParameter("linkingDataverseId", linkingDataverseId)
9797
.getSingleResult();
9898
} catch (jakarta.persistence.NoResultException e) {
99-
logger.fine("No DataverseLinkingDataverse found for dataverseId " + dataverseId + " and linkedDataverseId " + linkingDataverseId);
99+
logger.fine("No DataverseLinkingDataverse found for linkingDataverseId " + linkingDataverseId + " and linkedDataverseId " + linkedDataverseId);
100100
return null;
101101
}
102102
}
103103

104-
public boolean alreadyLinked(Dataverse definitionPoint, Dataverse dataverseToLinkTo) {
105-
return findDataverseLinkingDataverse(dataverseToLinkTo.getId(), definitionPoint.getId()) != null;
104+
public boolean alreadyLinked(Dataverse linkingDataverse, Dataverse linkedDataverse) {
105+
return findDataverseLinkingDataverse(linkingDataverse.getId(), linkedDataverse.getId()) != null;
106106
}
107107
}

src/main/java/edu/harvard/iq/dataverse/DataversePage.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -215,18 +215,31 @@ public void setLinkMode(LinkMode linkMode) {
215215
}
216216

217217
public boolean showLinkingPopup() {
218-
String testquery = "";
219-
if (session.getUser() == null) {
218+
// Must be logged in
219+
AuthenticatedUser au = getAuthenticatedUser();
220+
if (au == null) {
220221
return false;
221222
}
222223
if (dataverse == null) {
223224
return false;
224225
}
225-
if (query != null) {
226-
testquery = query;
226+
227+
// If there is an active search query, that's all that matters (plus having permission on ANY collection)
228+
if (query != null && !query.isEmpty()) {
229+
List<Dataverse> permitted = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), au, Permission.LinkDataverse);
230+
return permitted != null && !permitted.isEmpty();
231+
}
232+
233+
// Otherwise (no active search), check if there is at least one OTHER eligible collection
234+
// Eligible means: not the current collection and not in the parent tree
235+
// Technically, eligible also means "not already linked", but in that case, we show the Link button anyway and have the Link dialog display a message about all eligible collections already being linked
236+
List<Dataverse> dvsWithLinkPermission = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), au, Permission.LinkDataverse);
237+
if (dvsWithLinkPermission != null && !dvsWithLinkPermission.isEmpty()) {
238+
List<Dataverse> eligibleDataverses = dataverseService.removeUnlinkableDataverses(dvsWithLinkPermission, dataverse, false);
239+
return !eligibleDataverses.isEmpty();
227240
}
228241

229-
return (session.getUser().isSuperuser() && (dataverse.getOwner() != null || !testquery.isEmpty()));
242+
return false;
230243
}
231244

232245
public void setupLinkingPopup (String popupSetting){
@@ -241,35 +254,18 @@ public void setupLinkingPopup (String popupSetting){
241254
public void updateLinkableDataverses() {
242255
dataversesForLinking = new ArrayList<>();
243256
linkingDVSelectItems = new ArrayList<>();
244-
245-
//Since only a super user function add all dvs
246-
dataversesForLinking = dataverseService.findAll();// permissionService.getDataversesUserHasPermissionOn(session.getUser(), Permission.PublishDataverse);
247-
248-
/*
249-
List<DataverseRole> roles = dataverseRoleServiceBean.getDataverseRolesByPermission(Permission.PublishDataverse, dataverse.getId());
250-
List<String> types = new ArrayList();
251-
types.add("Dataverse");
252-
for (Long dvIdAsInt : permissionService.getDvObjectIdsUserHasRoleOn(session.getUser(), roles, types, false)) {
253-
dataversesForLinking.add(dataverseService.find(dvIdAsInt));
254-
}*/
255-
256-
//for linking - make sure the link hasn't occurred and its not int the tree
257-
if (this.linkMode.equals(LinkMode.LINKDATAVERSE)) {
258-
259-
// remove this and it's parent tree
260-
dataversesForLinking.remove(dataverse);
261-
Dataverse testDV = dataverse;
262-
while(testDV.getOwner() != null){
263-
dataversesForLinking.remove(testDV.getOwner());
264-
testDV = testDV.getOwner();
265-
}
266-
267-
for (Dataverse removeLinked : linkingService.findLinkingDataverses(dataverse.getId())) {
268-
dataversesForLinking.remove(removeLinked);
269-
}
270-
} else{
271-
//for saved search add all
272257

258+
259+
List<Dataverse> dvsWithLinkPermission = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), getAuthenticatedUser(), Permission.LinkDataverse, "");
260+
261+
if (dvsWithLinkPermission != null && !dvsWithLinkPermission.isEmpty()) {
262+
// for linking - make sure the link hasn't occurred and it's not in the tree
263+
if (this.linkMode.equals(LinkMode.LINKDATAVERSE)) {
264+
dataversesForLinking = dataverseService.removeUnlinkableDataverses(dvsWithLinkPermission, dataverse);
265+
} else {
266+
// for saved search, add all
267+
dataversesForLinking = dvsWithLinkPermission;
268+
}
273269
}
274270

275271
for (Dataverse selectDV : dataversesForLinking) {

src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -551,28 +551,30 @@ public List<Dataverse> filterDataversesForLinking(String query, DataverseRequest
551551
return dataverseList;
552552
}
553553

554-
public List<Dataverse> removeUnlinkableDataverses(List<Dataverse> allWithPerms, DvObject dvo) {
554+
public List<Dataverse> removeUnlinkableDataverses(List<Dataverse> allWithPerms, DvObject dvo, boolean removeAlreadyLinked) {
555555
List<Dataverse> dataverseList = new ArrayList<>();
556556
Dataset linkedDataset = null;
557557
Dataverse linkedDataverse = null;
558-
List<Object> alreadyLinkeddv_ids;
559-
560-
if ((dvo instanceof Dataset)) {
561-
linkedDataset = (Dataset) dvo;
562-
alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + linkedDataset.getId()).getResultList();
563-
} else {
564-
linkedDataverse = (Dataverse) dvo;
565-
alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList();
566-
}
567558

568559
List<Dataverse> remove = new ArrayList<>();
569560

570-
if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) {
571-
alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((removeIt) -> {
572-
remove.add(removeIt);
573-
});
561+
if (removeAlreadyLinked) {
562+
List<Object> alreadyLinkeddv_ids;
563+
564+
if ((dvo instanceof Dataset)) {
565+
linkedDataset = (Dataset) dvo;
566+
alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + linkedDataset.getId()).getResultList();
567+
} else {
568+
linkedDataverse = (Dataverse) dvo;
569+
alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList();
570+
}
571+
572+
if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) {
573+
alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((removeIt) -> {
574+
remove.add(removeIt);
575+
});
576+
}
574577
}
575-
576578

577579
if (dvo instanceof Dataverse dataverse) {
578580
remove.add(dataverse);
@@ -596,8 +598,11 @@ public List<Dataverse> removeUnlinkableDataverses(List<Dataverse> allWithPerms,
596598

597599
return dataverseList;
598600
}
599-
600-
601+
602+
public List<Dataverse> removeUnlinkableDataverses(List<Dataverse> allWithPerms, DvObject dvo) {
603+
return removeUnlinkableDataverses(allWithPerms, dvo, true);
604+
}
605+
601606
public List<Dataverse> filterDataversesForUnLinking(String query, DataverseRequest req, Dataset dataset) {
602607
List<Object> alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + dataset.getId()).getResultList();
603608
List<Dataverse> dataverseList = new ArrayList<>();

src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ private List<DatasetFieldType> parseFacets(JsonArray facetsArray) throws Wrapped
855855

856856
@DELETE
857857
@AuthRequired
858-
@Path("{linkingDataverseId}/deleteLink/{linkedDataverseId}")
858+
@Path("{linkedDataverseId}/deleteLink/{linkingDataverseId}")
859859
public Response deleteDataverseLinkingDataverse(@Context ContainerRequestContext crc, @PathParam("linkingDataverseId") String linkingDataverseId, @PathParam("linkedDataverseId") String linkedDataverseId) {
860860
boolean index = true;
861861
return response(req -> {

src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateSavedSearchCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import java.util.logging.Logger;
1515
import jakarta.json.JsonObjectBuilder;
1616

17-
@RequiredPermissions(Permission.PublishDataverse)
17+
@RequiredPermissions(Permission.LinkDataverse)
1818
public class CreateSavedSearchCommand extends AbstractCommand<SavedSearch> {
1919

2020
private static final Logger logger = Logger.getLogger(SavedSearchServiceBean.class.getCanonicalName());

src/main/java/edu/harvard/iq/dataverse/engine/command/impl/DeleteDataverseLinkingDataverseCommand.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @author sarahferry
2727
*/
2828

29-
@RequiredPermissions( Permission.EditDataverse )
29+
@RequiredPermissions( Permission.LinkDataverse )
3030
public class DeleteDataverseLinkingDataverseCommand extends AbstractCommand<Dataverse> {
3131

3232
private final DataverseLinkingDataverse doomed;
@@ -42,10 +42,6 @@ public DeleteDataverseLinkingDataverseCommand(DataverseRequest aRequest, Dataver
4242

4343
@Override
4444
public Dataverse execute(CommandContext ctxt) throws CommandException {
45-
if ((!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser())) {
46-
throw new PermissionException("Delete dataverse linking dataverse can only be called by superusers.",
47-
this, Collections.singleton(Permission.DeleteDataverse), editedDv);
48-
}
4945
Dataverse merged = ctxt.em().merge(editedDv);
5046
DataverseLinkingDataverse doomedAndMerged = ctxt.em().merge(doomed);
5147
ctxt.em().remove(doomedAndMerged);

0 commit comments

Comments
 (0)