From d6598ccb14a10d692092969b4b6db517cc37c82d Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 9 Sep 2026 10:01:39 -0400 Subject: [PATCH] Revert "Merge pull request #12250 from vera/feat/non-superuser-dataverse-linking" This reverts commit 16ea4cc48d0d3964614fef4e906f6fe01d715a03, reversing changes made to 0df5da7e4dd1a885eb706381b549a76ce37fb40a. --- .../12076-non-superuser-dataverse-linking.md | 1 - .../source/admin/dataverses-datasets.rst | 4 +- doc/sphinx-guides/source/api/native-api.rst | 60 ------------- .../source/user/dataverse-management.rst | 8 +- .../DataverseLinkingServiceBean.java | 10 +-- .../harvard/iq/dataverse/DataversePage.java | 62 ++++++------- .../iq/dataverse/DataverseServiceBean.java | 39 ++++----- .../harvard/iq/dataverse/api/Dataverses.java | 2 +- .../impl/CreateSavedSearchCommand.java | 2 +- ...eleteDataverseLinkingDataverseCommand.java | 6 +- .../command/impl/LinkDataverseCommand.java | 4 + .../savedsearch/SavedSearchServiceBean.java | 2 +- src/main/webapp/dataverse.xhtml | 7 +- .../edu/harvard/iq/dataverse/api/LinkIT.java | 86 +++++++------------ 14 files changed, 106 insertions(+), 187 deletions(-) delete mode 100644 doc/release-notes/12076-non-superuser-dataverse-linking.md diff --git a/doc/release-notes/12076-non-superuser-dataverse-linking.md b/doc/release-notes/12076-non-superuser-dataverse-linking.md deleted file mode 100644 index 6ed55b65031..00000000000 --- a/doc/release-notes/12076-non-superuser-dataverse-linking.md +++ /dev/null @@ -1 +0,0 @@ -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. diff --git a/doc/sphinx-guides/source/admin/dataverses-datasets.rst b/doc/sphinx-guides/source/admin/dataverses-datasets.rst index dc4dfabfedc..c5820422953 100644 --- a/doc/sphinx-guides/source/admin/dataverses-datasets.rst +++ b/doc/sphinx-guides/source/admin/dataverses-datasets.rst @@ -22,14 +22,14 @@ Moves a Dataverse collection whose id is passed to an existing Dataverse collect Link a Dataverse Collection ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Creates a link between a Dataverse collection and another Dataverse collection (see the :ref:`dataverse-linking` section of the User Guide for more information). :: +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. :: curl -H "X-Dataverse-key: $API_TOKEN" -X PUT http://$SERVER/api/dataverses/$linked-dataverse-alias/link/$linking-dataverse-alias Unlink a Dataverse Collection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Removes a link between a Dataverse collection and another Dataverse collection. Accessible to users with Link Dataverse permission on the linking Dataverse collection. :: +Removes a link between a Dataverse collection and another Dataverse collection. Only accessible to superusers. :: curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE http://$SERVER/api/dataverses/$linked-dataverse-alias/deleteLink/$linking-dataverse-alias diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 1806772afbd..1008bf9d765 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -3750,66 +3750,6 @@ The fully expanded example above (without environment variables) looks like this curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X PUT "https://demo.dataverse.org/api/datasets/24/link/test" -Unlink a Dataset -~~~~~~~~~~~~~~~~ - -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): - -.. code-block:: bash - - export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - export SERVER_URL=https://demo.dataverse.org - export DATASET_ID=24 - export DATAVERSE_ID=test - - curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/datasets/$DATASET_ID/deleteLink/$DATAVERSE_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/24/deleteLink/test" - -Link a Dataverse collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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): - -.. code-block:: bash - - export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - export SERVER_URL=https://demo.dataverse.org - export LINKED_DATAVERSE_ID=linked-collection - export LINKING_DATAVERSE_ID=linking-collection - - curl -H "X-Dataverse-key: $API_TOKEN" -X PUT "$SERVER_URL/api/dataverses/$LINKED_DATAVERSE_ID/link/$LINKING_DATAVERSE_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 PUT "https://demo.dataverse.org/api/dataverses/linked-collection/link/linking-collection" - -Unlink a Dataverse collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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): - -.. code-block:: bash - - export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - export SERVER_URL=https://demo.dataverse.org - export LINKED_DATAVERSE_ID=linked-collection - export LINKING_DATAVERSE_ID=linking-collection - - curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/dataverses/$LINKED_DATAVERSE_ID/deleteLink/$LINKING_DATAVERSE_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/dataverses/linked-collection/deleteLink/linking-collection" - Dataset Locks ~~~~~~~~~~~~~ diff --git a/doc/sphinx-guides/source/user/dataverse-management.rst b/doc/sphinx-guides/source/user/dataverse-management.rst index 7cded79f9bf..4799d2cdb30 100755 --- a/doc/sphinx-guides/source/user/dataverse-management.rst +++ b/doc/sphinx-guides/source/user/dataverse-management.rst @@ -221,18 +221,18 @@ In order to link a dataset, you will need your account to have the "Link Dataset 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. -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. - 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. +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). + .. _dataverse-linking: Dataverse Collection Linking ============================ -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. +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. -In order to link a collection, you will need your account to have the "Link Dataverse" permission on the linking Dataverse collection. +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. Publish Your Dataverse Collection ================================= diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java index cd806d250d4..9f1bcde4c0e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java @@ -89,19 +89,19 @@ public void save(DataverseLinkingDataverse dataverseLinkingDataverse) { } } - public DataverseLinkingDataverse findDataverseLinkingDataverse(Long linkingDataverseId, Long linkedDataverseId) { + public DataverseLinkingDataverse findDataverseLinkingDataverse(Long dataverseId, Long linkingDataverseId) { try { return em.createNamedQuery("DataverseLinkingDataverse.findByDataverseIdAndLinkingDataverseId", DataverseLinkingDataverse.class) - .setParameter("dataverseId", linkedDataverseId) + .setParameter("dataverseId", dataverseId) .setParameter("linkingDataverseId", linkingDataverseId) .getSingleResult(); } catch (jakarta.persistence.NoResultException e) { - logger.fine("No DataverseLinkingDataverse found for linkingDataverseId " + linkingDataverseId + " and linkedDataverseId " + linkedDataverseId); + logger.fine("No DataverseLinkingDataverse found for dataverseId " + dataverseId + " and linkedDataverseId " + linkingDataverseId); return null; } } - public boolean alreadyLinked(Dataverse linkingDataverse, Dataverse linkedDataverse) { - return findDataverseLinkingDataverse(linkingDataverse.getId(), linkedDataverse.getId()) != null; + public boolean alreadyLinked(Dataverse definitionPoint, Dataverse dataverseToLinkTo) { + return findDataverseLinkingDataverse(dataverseToLinkTo.getId(), definitionPoint.getId()) != null; } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DataversePage.java b/src/main/java/edu/harvard/iq/dataverse/DataversePage.java index 76ef1002f7e..a8fca620fbe 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataversePage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataversePage.java @@ -215,31 +215,18 @@ public void setLinkMode(LinkMode linkMode) { } public boolean showLinkingPopup() { - // Must be logged in - AuthenticatedUser au = getAuthenticatedUser(); - if (au == null) { + String testquery = ""; + if (session.getUser() == null) { return false; } if (dataverse == null) { return false; } - - // If there is an active search query, that's all that matters (plus having permission on ANY collection) - if (query != null && !query.isEmpty()) { - List permitted = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), au, Permission.LinkDataverse); - return permitted != null && !permitted.isEmpty(); - } - - // Otherwise (no active search), check if there is at least one OTHER eligible collection - // Eligible means: not the current collection and not in the parent tree - // 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 - List dvsWithLinkPermission = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), au, Permission.LinkDataverse); - if (dvsWithLinkPermission != null && !dvsWithLinkPermission.isEmpty()) { - List eligibleDataverses = dataverseService.removeUnlinkableDataverses(dvsWithLinkPermission, dataverse, false); - return !eligibleDataverses.isEmpty(); + if (query != null) { + testquery = query; } - return false; + return (session.getUser().isSuperuser() && (dataverse.getOwner() != null || !testquery.isEmpty())); } public void setupLinkingPopup (String popupSetting){ @@ -254,18 +241,35 @@ public void setupLinkingPopup (String popupSetting){ public void updateLinkableDataverses() { dataversesForLinking = new ArrayList<>(); linkingDVSelectItems = new ArrayList<>(); - - - List dvsWithLinkPermission = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), getAuthenticatedUser(), Permission.LinkDataverse, ""); - - if (dvsWithLinkPermission != null && !dvsWithLinkPermission.isEmpty()) { - // for linking - make sure the link hasn't occurred and it's not in the tree - if (this.linkMode.equals(LinkMode.LINKDATAVERSE)) { - dataversesForLinking = dataverseService.removeUnlinkableDataverses(dvsWithLinkPermission, dataverse); - } else { - // for saved search, add all - dataversesForLinking = dvsWithLinkPermission; + + //Since only a super user function add all dvs + dataversesForLinking = dataverseService.findAll();// permissionService.getDataversesUserHasPermissionOn(session.getUser(), Permission.PublishDataverse); + + /* + List roles = dataverseRoleServiceBean.getDataverseRolesByPermission(Permission.PublishDataverse, dataverse.getId()); + List types = new ArrayList(); + types.add("Dataverse"); + for (Long dvIdAsInt : permissionService.getDvObjectIdsUserHasRoleOn(session.getUser(), roles, types, false)) { + dataversesForLinking.add(dataverseService.find(dvIdAsInt)); + }*/ + + //for linking - make sure the link hasn't occurred and its not int the tree + if (this.linkMode.equals(LinkMode.LINKDATAVERSE)) { + + // remove this and it's parent tree + dataversesForLinking.remove(dataverse); + Dataverse testDV = dataverse; + while(testDV.getOwner() != null){ + dataversesForLinking.remove(testDV.getOwner()); + testDV = testDV.getOwner(); + } + + for (Dataverse removeLinked : linkingService.findLinkingDataverses(dataverse.getId())) { + dataversesForLinking.remove(removeLinked); } + } else{ + //for saved search add all + } for (Dataverse selectDV : dataversesForLinking) { diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index d06ca2cb5de..c06c7f6fc76 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -552,30 +552,28 @@ public List filterDataversesForLinking(String query, DataverseRequest return dataverseList; } - public List removeUnlinkableDataverses(List allWithPerms, DvObject dvo, boolean removeAlreadyLinked) { + public List removeUnlinkableDataverses(List allWithPerms, DvObject dvo) { List dataverseList = new ArrayList<>(); Dataset linkedDataset = null; Dataverse linkedDataverse = null; + List alreadyLinkeddv_ids; - List remove = new ArrayList<>(); - - if (removeAlreadyLinked) { - List alreadyLinkeddv_ids; + if ((dvo instanceof Dataset)) { + linkedDataset = (Dataset) dvo; + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + linkedDataset.getId()).getResultList(); + } else { + linkedDataverse = (Dataverse) dvo; + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList(); + } - if ((dvo instanceof Dataset)) { - linkedDataset = (Dataset) dvo; - alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + linkedDataset.getId()).getResultList(); - } else { - linkedDataverse = (Dataverse) dvo; - alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList(); - } + List remove = new ArrayList<>(); - if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) { - alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((removeIt) -> { - remove.add(removeIt); - }); - } + if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) { + alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((removeIt) -> { + remove.add(removeIt); + }); } + if (dvo instanceof Dataverse dataverse) { remove.add(dataverse); @@ -599,11 +597,8 @@ public List removeUnlinkableDataverses(List allWithPerms, return dataverseList; } - - public List removeUnlinkableDataverses(List allWithPerms, DvObject dvo) { - return removeUnlinkableDataverses(allWithPerms, dvo, true); - } - + + public List filterDataversesForUnLinking(String query, DataverseRequest req, Dataset dataset) { List alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + dataset.getId()).getResultList(); List dataverseList = new ArrayList<>(); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index 8e4a83f48b9..ee45981162a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -950,7 +950,7 @@ private List parseFacets(JsonArray facetsArray) throws Wrapped @DELETE @AuthRequired - @Path("{linkedDataverseId}/deleteLink/{linkingDataverseId}") + @Path("{linkingDataverseId}/deleteLink/{linkedDataverseId}") @Operation(summary = "Remove a dataverse link", description = "Deletes a link from one dataverse to another linked dataverse.") public Response deleteDataverseLinkingDataverse(@Context ContainerRequestContext crc, diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateSavedSearchCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateSavedSearchCommand.java index ff3572e2ee8..7a549a51dd5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateSavedSearchCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateSavedSearchCommand.java @@ -14,7 +14,7 @@ import java.util.logging.Logger; import jakarta.json.JsonObjectBuilder; -@RequiredPermissions(Permission.LinkDataverse) +@RequiredPermissions(Permission.PublishDataverse) public class CreateSavedSearchCommand extends AbstractCommand { private static final Logger logger = Logger.getLogger(SavedSearchServiceBean.class.getCanonicalName()); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/DeleteDataverseLinkingDataverseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/DeleteDataverseLinkingDataverseCommand.java index 33f88d8d768..9eddfcd2b9c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/DeleteDataverseLinkingDataverseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/DeleteDataverseLinkingDataverseCommand.java @@ -26,7 +26,7 @@ * @author sarahferry */ -@RequiredPermissions( Permission.LinkDataverse ) +@RequiredPermissions( Permission.EditDataverse ) public class DeleteDataverseLinkingDataverseCommand extends AbstractCommand { private final DataverseLinkingDataverse doomed; @@ -42,6 +42,10 @@ public DeleteDataverseLinkingDataverseCommand(DataverseRequest aRequest, Dataver @Override public Dataverse execute(CommandContext ctxt) throws CommandException { + if ((!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser())) { + throw new PermissionException("Delete dataverse linking dataverse can only be called by superusers.", + this, Collections.singleton(Permission.DeleteDataverse), editedDv); + } Dataverse merged = ctxt.em().merge(editedDv); DataverseLinkingDataverse doomedAndMerged = ctxt.em().merge(doomed); ctxt.em().remove(doomedAndMerged); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LinkDataverseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LinkDataverseCommand.java index 3561aba076c..2e1aecc9a84 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LinkDataverseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LinkDataverseCommand.java @@ -45,6 +45,10 @@ public LinkDataverseCommand(DataverseRequest aRequest, Dataverse dataverse, Data @Override 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.LinkDataverse), linkingDataverse); + } if (linkedDataverse.equals(linkingDataverse)) { throw new IllegalCommandException("Can't link a dataverse to itself", this); } diff --git a/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java index 570e3939ab2..cbb0b73212e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java @@ -277,7 +277,7 @@ public void removeLinks(DataverseRequest dvReq, SavedSearch savedSearch) throws if (dvObjectThatDefinitionPointWillLinkTo.isInstanceofDataverse()) { Dataverse linkedDataverse = (Dataverse) dvObjectThatDefinitionPointWillLinkTo; - DataverseLinkingDataverse dvld = dvLinkingService.findDataverseLinkingDataverse(linkingDataverse.getId(), linkedDataverse.getId()); + DataverseLinkingDataverse dvld = dvLinkingService.findDataverseLinkingDataverse(linkedDataverse.getId(), linkingDataverse.getId()); if(dvld != null) { Dataverse dv = commandEngine.submitInNewTransaction(new DeleteDataverseLinkingDataverseCommand(dvReq, linkingDataverse, dvld, true)); } diff --git a/src/main/webapp/dataverse.xhtml b/src/main/webapp/dataverse.xhtml index 5a42b4b1f8e..59c374eec0b 100644 --- a/src/main/webapp/dataverse.xhtml +++ b/src/main/webapp/dataverse.xhtml @@ -518,8 +518,7 @@
+ or permissionsWrapper.canIssuePublishDataverseCommand(dataverse))}">
- + @@ -868,7 +867,7 @@

- #{bundle['dataverse.link.no.linkable.remaining']} + #{bundle['dataverse.link.no.linkable']}