Skip to content

Commit a896955

Browse files
authored
Merge pull request #11492 from vera/feat/list-dataset-links
feat: allow non-superusers to list the links of a dataset via API
2 parents b330d21 + f93ee36 commit a896955

File tree

4 files changed

+183
-16
lines changed

4 files changed

+183
-16
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The [API for listing the collections a dataset has been linked to](https://guides.dataverse.org/en/latest/admin/dataverses-datasets.html#list-collections-that-are-linked-from-a-dataset) (`api/datasets/$linked-dataset-id/links`) is no longer restricted to superusers. For unpublished datasets, users need the "View Unpublished Dataset" permission to access the API. Unpublished collections in the list require the "View Unpublished Dataverse" permission; otherwise, they are hidden.

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,20 +2086,23 @@ public Response getCustomTermsTab(@PathParam("id") String id, @PathParam("versio
20862086
public Response getLinks(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied ) {
20872087
try {
20882088
User u = getRequestUser(crc);
2089-
if (!u.isSuperuser()) {
2090-
return error(Response.Status.FORBIDDEN, "Not a superuser");
2091-
}
20922089
Dataset dataset = findDatasetOrDie(idSupplied);
20932090

2091+
if (!dataset.isReleased() && !permissionService.hasPermissionsFor(u, dataset, EnumSet.of(Permission.ViewUnpublishedDataset))) {
2092+
return error(Response.Status.FORBIDDEN, "User is not allowed to list the link(s) of this dataset");
2093+
}
2094+
20942095
long datasetId = dataset.getId();
20952096
List<Dataverse> dvsThatLinkToThisDatasetId = dataverseSvc.findDataversesThatLinkToThisDatasetId(datasetId);
20962097
JsonArrayBuilder dataversesThatLinkToThisDatasetIdBuilder = Json.createArrayBuilder();
20972098
for (Dataverse dataverse : dvsThatLinkToThisDatasetId) {
2098-
JsonObjectBuilder datasetBuilder = Json.createObjectBuilder();
2099-
datasetBuilder.add("id", dataverse.getId());
2100-
datasetBuilder.add("alias", dataverse.getAlias());
2101-
datasetBuilder.add("displayName", dataverse.getDisplayName());
2102-
dataversesThatLinkToThisDatasetIdBuilder.add(datasetBuilder.build());
2099+
if (dataverse.isReleased() || this.permissionService.hasPermissionsFor(u, dataverse, EnumSet.of(Permission.ViewUnpublishedDataverse))) {
2100+
JsonObjectBuilder datasetBuilder = Json.createObjectBuilder();
2101+
datasetBuilder.add("id", dataverse.getId());
2102+
datasetBuilder.add("alias", dataverse.getAlias());
2103+
datasetBuilder.add("displayName", dataverse.getDisplayName());
2104+
dataversesThatLinkToThisDatasetIdBuilder.add(datasetBuilder.build());
2105+
}
21032106
}
21042107
JsonObjectBuilder response = Json.createObjectBuilder();
21052108
response.add("id", datasetId);

src/test/java/edu/harvard/iq/dataverse/api/LinkIT.java

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
import io.restassured.path.json.JsonPath;
55
import io.restassured.response.Response;
66

7+
import java.io.StringReader;
8+
import java.util.ArrayList;
9+
import java.util.List;
710
import java.util.logging.Logger;
8-
import static jakarta.ws.rs.core.Response.Status.CREATED;
9-
import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
10-
import static jakarta.ws.rs.core.Response.Status.OK;
11+
12+
import static jakarta.ws.rs.core.Response.Status.*;
1113
import static org.hamcrest.CoreMatchers.equalTo;
12-
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
import static org.junit.jupiter.api.Assertions.*;
1315

16+
import jakarta.json.Json;
17+
import jakarta.json.JsonArray;
18+
import jakarta.json.JsonObject;
1419
import org.junit.jupiter.api.BeforeAll;
1520
import org.junit.jupiter.api.Test;
1621

@@ -186,4 +191,160 @@ public void testDeepLinks() {
186191

187192
}
188193

194+
@Test
195+
public void testListLinks() {
196+
197+
Response createUser1 = UtilIT.createRandomUser();
198+
createUser1.prettyPrint();
199+
createUser1.then().assertThat()
200+
.statusCode(OK.getStatusCode());
201+
String apiToken1 = UtilIT.getApiTokenFromResponse(createUser1);
202+
203+
Response createUser2 = UtilIT.createRandomUser();
204+
createUser2.prettyPrint();
205+
createUser2.then().assertThat()
206+
.statusCode(OK.getStatusCode());
207+
String username2 = UtilIT.getUsernameFromResponse(createUser2);
208+
String apiToken2 = UtilIT.getApiTokenFromResponse(createUser2);
209+
210+
// Create and publish dataverse1 which both user1 and user2 have admin access to
211+
Response createDataverse1 = UtilIT.createRandomDataverse(apiToken1);
212+
createDataverse1.prettyPrint();
213+
createDataverse1.then().assertThat()
214+
.statusCode(CREATED.getStatusCode());
215+
String dataverse1Alias = UtilIT.getAliasFromResponse(createDataverse1);
216+
217+
UtilIT.publishDataverseViaNativeApi(dataverse1Alias, apiToken1).then().assertThat()
218+
.statusCode(OK.getStatusCode());
219+
220+
Response grantUser2AccessOnDataverse = UtilIT.grantRoleOnDataverse(dataverse1Alias, "admin", "@" + username2, apiToken1);
221+
grantUser2AccessOnDataverse.prettyPrint();
222+
grantUser2AccessOnDataverse.then().assertThat()
223+
.statusCode(OK.getStatusCode());
224+
225+
// Create and publish dataset in dataverse1
226+
// Which means that both user1 and user2 have permission to view it
227+
Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverse1Alias, apiToken1);
228+
createDataset.prettyPrint();
229+
createDataset.then().assertThat()
230+
.statusCode(CREATED.getStatusCode());
231+
String datasetPid = JsonPath.from(createDataset.asString()).getString("data.persistentId");
232+
233+
Response publishDatasetResponse = UtilIT.publishDatasetViaNativeApi(datasetPid, "major", apiToken1);
234+
publishDatasetResponse.prettyPrint();
235+
publishDatasetResponse.then().assertThat()
236+
.statusCode(OK.getStatusCode());
237+
238+
// Create another unpublished dataverse2 as user2, and don't grant user1 any permissions on it
239+
// Which means that user1 doesn't have permission to view this dataverse before it is published
240+
Response createDataverse2 = UtilIT.createRandomDataverse(apiToken2);
241+
createDataverse2.prettyPrint();
242+
createDataverse2.then().assertThat()
243+
.statusCode(CREATED.getStatusCode());
244+
String dataverse2Alias = UtilIT.getAliasFromResponse(createDataverse2);
245+
Integer dataverse2Id = UtilIT.getDatasetIdFromResponse(createDataverse2);
246+
247+
// User1 doesn't have permission to link the dataset to the unpublished dataverse2
248+
Response tryToLinkToUnpublishedDataverseResponse = UtilIT.linkDataset(datasetPid, dataverse2Alias, apiToken1);
249+
tryToLinkToUnpublishedDataverseResponse.prettyPrint();
250+
tryToLinkToUnpublishedDataverseResponse.then().assertThat()
251+
.statusCode(UNAUTHORIZED.getStatusCode());
252+
253+
// But user2 does have permission to link the dataset to his own unpublished dataverse2
254+
Response linkDatasetToUnpublishedDataverseResponse = UtilIT.linkDataset(datasetPid, dataverse2Alias, apiToken2);
255+
linkDatasetToUnpublishedDataverseResponse.prettyPrint();
256+
linkDatasetToUnpublishedDataverseResponse.then().assertThat()
257+
.statusCode(OK.getStatusCode());
258+
259+
// User1 has permission to list the links of the dataset, but cannot see the link to the unpublished dataverse2
260+
Response linkDatasetsResponse = UtilIT.getDatasetLinks(datasetPid, apiToken1);
261+
linkDatasetsResponse.prettyPrint();
262+
linkDatasetsResponse.then().assertThat()
263+
.statusCode(OK.getStatusCode());
264+
JsonObject linkDatasets = Json.createReader(new StringReader(linkDatasetsResponse.asString())).readObject();
265+
JsonArray linksList = linkDatasets.getJsonObject("data").getJsonArray("linked-dataverses");
266+
assertEquals(0, linksList.size());
267+
268+
// User2 has permission to list the links of the dataset and can see the link to the unpublished dataverse2
269+
Response linkDatasetsResponse2 = UtilIT.getDatasetLinks(datasetPid, apiToken2);
270+
linkDatasetsResponse2.prettyPrint();
271+
linkDatasetsResponse2.then().assertThat()
272+
.statusCode(OK.getStatusCode());
273+
JsonObject linkDatasets2 = Json.createReader(new StringReader(linkDatasetsResponse2.asString())).readObject();
274+
JsonArray linksList2 = linkDatasets2.getJsonObject("data").getJsonArray("linked-dataverses");
275+
assertEquals(1, linksList2.size());
276+
assertEquals(dataverse2Id, linksList2.getJsonObject(0).getInt("id"));
277+
278+
UtilIT.publishDataverseViaNativeApi(dataverse2Alias, apiToken2).then().assertThat()
279+
.statusCode(OK.getStatusCode());
280+
281+
// After publishing dataverse2, user1 can now also see the link
282+
Response linkDatasetsResponse3 = UtilIT.getDatasetLinks(datasetPid, apiToken1);
283+
linkDatasetsResponse3.prettyPrint();
284+
linkDatasetsResponse3.then().assertThat()
285+
.statusCode(OK.getStatusCode());
286+
JsonObject linkDatasets3 = Json.createReader(new StringReader(linkDatasetsResponse3.asString())).readObject();
287+
JsonArray linksList3 = linkDatasets3.getJsonObject("data").getJsonArray("linked-dataverses");
288+
assertEquals(1, linksList3.size());
289+
assertEquals(dataverse2Id, linksList3.getJsonObject(0).getInt("id"));
290+
291+
// Create another dataset, but don't publish it
292+
Response createDataset2 = UtilIT.createRandomDatasetViaNativeApi(dataverse1Alias, apiToken1);
293+
createDataset2.prettyPrint();
294+
createDataset2.then().assertThat()
295+
.statusCode(CREATED.getStatusCode());
296+
String dataset2Pid = JsonPath.from(createDataset2.asString()).getString("data.persistentId");
297+
298+
// Create user3 without permissions on the unpublished dataset
299+
Response createUser3 = UtilIT.createRandomUser();
300+
createUser3.prettyPrint();
301+
createUser3.then().assertThat()
302+
.statusCode(OK.getStatusCode());
303+
String username3 = UtilIT.getUsernameFromResponse(createUser3);
304+
String apiToken3 = UtilIT.getApiTokenFromResponse(createUser3);
305+
306+
// User3 cannot list the links of the unpublished dataset
307+
Response linkDatasetsResponse4 = UtilIT.getDatasetLinks(dataset2Pid, apiToken3);
308+
linkDatasetsResponse4.prettyPrint();
309+
linkDatasetsResponse4.then().assertThat()
310+
.statusCode(FORBIDDEN.getStatusCode());
311+
312+
// Grant user3 "member" role on dataverse1, which allows viewing unpublished datasets
313+
Response grantUser3AccessOnDataverse = UtilIT.grantRoleOnDataverse(dataverse1Alias, "member", "@" + username3, apiToken1);
314+
grantUser3AccessOnDataverse.prettyPrint();
315+
grantUser3AccessOnDataverse.then().assertThat()
316+
.statusCode(OK.getStatusCode());
317+
318+
// User 3 can now also list the links
319+
Response linkDatasetsResponse5 = UtilIT.getDatasetLinks(dataset2Pid, apiToken3);
320+
linkDatasetsResponse5.prettyPrint();
321+
linkDatasetsResponse5.then().assertThat()
322+
.statusCode(OK.getStatusCode());
323+
JsonObject linkDatasets5 = Json.createReader(new StringReader(linkDatasetsResponse5.asString())).readObject();
324+
JsonArray linksList5 = linkDatasets5.getJsonObject("data").getJsonArray("linked-dataverses");
325+
assertEquals(0, linksList5.size());
326+
327+
// Non-authenticated user cannot list the links of the unpublished dataset
328+
Response linkDatasetsResponse6 = UtilIT.getDatasetLinks(dataset2Pid, null);
329+
linkDatasetsResponse6.prettyPrint();
330+
linkDatasetsResponse6.then().assertThat()
331+
.statusCode(FORBIDDEN.getStatusCode());
332+
333+
// Publish dataset2
334+
Response publishDataset2Response = UtilIT.publishDatasetViaNativeApi(dataset2Pid, "major", apiToken1);
335+
publishDataset2Response.prettyPrint();
336+
publishDataset2Response.then().assertThat()
337+
.statusCode(OK.getStatusCode());
338+
339+
// After publishing the dataset, non-authenticated user can now also list the links
340+
Response linkDatasetsResponse7 = UtilIT.getDatasetLinks(dataset2Pid, null);
341+
linkDatasetsResponse7.prettyPrint();
342+
linkDatasetsResponse7.then().assertThat()
343+
.statusCode(OK.getStatusCode());
344+
JsonObject linkDatasets7 = Json.createReader(new StringReader(linkDatasetsResponse7.asString())).readObject();
345+
JsonArray linksList7 = linkDatasets7.getJsonObject("data").getJsonArray("linked-dataverses");
346+
assertEquals(0, linksList7.size());
347+
348+
}
349+
189350
}

src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,10 +2095,12 @@ static Response linkDataset(String datasetToLinkPid, String dataverseAlias, Stri
20952095
}
20962096

20972097
static Response getDatasetLinks(String datasetToLinkPid, String apiToken) {
2098-
Response response = given()
2099-
.header(API_TOKEN_HTTP_HEADER, apiToken)
2100-
.get("api/datasets/:persistentId/links" + "?persistentId=" + datasetToLinkPid);
2101-
return response;
2098+
RequestSpecification requestSpecification = given();
2099+
if (apiToken != null) {
2100+
requestSpecification = given()
2101+
.header(UtilIT.API_TOKEN_HTTP_HEADER, apiToken);
2102+
}
2103+
return requestSpecification.get("api/datasets/:persistentId/links" + "?persistentId=" + datasetToLinkPid);
21022104
}
21032105

21042106
static Response createDataverseLink(String linkedDataverseAlias, String linkingDataverseAlias, String apiToken) {

0 commit comments

Comments
 (0)