Skip to content

Commit cb1ad3d

Browse files
authored
Merge pull request #11904 from GlobalDataverseCommunityConsortium/TKLabels
Adapt to LC's change to use doi array
2 parents 838af59 + 28f360f commit cb1ad3d

File tree

7 files changed

+199
-92
lines changed

7 files changed

+199
-92
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Updates to Local Contexts integration
2+
3+
- Integration with Local Contexts (https://guides.dataverse.org/en/latest/installation/localcontexts.html) has been updated to support the change in their API w.r.t. how DOIs entered as "Optional Project Information" are represented.

doc/sphinx-guides/source/installation/config.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3720,8 +3720,8 @@ The default value when not set is "chicago-author-date, ieee".
37203720

37213721
.. _localcontexts:
37223722

3723-
localcontexts.url
3724-
+++++++++++++++++
3723+
dataverse.localcontexts.url
3724+
+++++++++++++++++++++++++++
37253725

37263726
.. note::
37273727
For more information about LocalContexts integration, see :doc:`/installation/localcontexts`.
@@ -3733,8 +3733,8 @@ The URL for the Local Contexts Hub API.
37333733
37343734
Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_LOCALCONTEXTS_URL``.
37353735

3736-
localcontexts.api-key
3737-
+++++++++++++++++++++
3736+
dataverse.localcontexts.api-key
3737+
+++++++++++++++++++++++++++++++
37383738

37393739
The API key for accessing the Local Contexts Hub.
37403740

doc/sphinx-guides/source/installation/localcontexts.rst

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,16 @@ Configuration
1818

1919
There are several steps to LocalContexts integration.
2020

21-
First, you need to configure the LOCALCONTEXTS_URL and LOCALCONTEXTS_API_KEY as described in the :ref:`localcontexts` section of the Configuration Guide.
22-
API Keys are available to Local Contexts Integration Partners - see https://localcontexts.org/hub-agreements/integration-partners/ for details.
23-
24-
Next, you should add the Local Contexts metadatablock and configure the associated external vocabulary script.
25-
The metadatablock contains one field allowing Dataverse to store the URL of an associated Local Contexts Hub project.
26-
The external vocabulary script interacts with the Local Contexts Hub (via the Dataverse server) to display the Labels and Notices associated with the proect and provide a link to it.
27-
The script also supports adding/removing such a link from the dataset's metadata. Note that only a project that references the dataset in its `publication_doi` field can be linked to a dataset.
28-
See https://github.com/gdcc/dataverse-external-vocab-support/blob/main/packages/local_contexts/README.md for details on these steps.
29-
30-
Lastly, if you wish the Local Contexts information to be shown in the summary section of the dataset page, as shown in the image above, you should add `LCProjectUrl` to list of custom summary fields via use of the :ref:`:CustomDatasetSummaryFields` setting.
31-
32-
Optionally, one could also set the dataverse.feature.add-local-contexts-permission-check FeatureFlag to true. This assures that only users editing datasets can use the LocalContexts search functionality.
33-
However, as this currently would also require setting the dataverse.feature.api-session-auth, the security implications of which haven't been fully explored, it is not recommended unless problematic use is seen.
34-
(When API access via OpenIdConnect is available, use of api-session-auth would not be required.)
21+
- A Local Contexts Hub Institutional or Integration Partner account is required. See https://localcontexts.org/hub-agreements for more information and the associated costs.
22+
(Institutions may wish to connect their Institutional accounts with `The Dataverse Project Integration Partner <https://localcontexts.org/integration-partners/view/12>`_ rather than having their own Integration Partner account.)
23+
(Free accounts for testing can be created at https://sandbox.localcontextshub.org/.)
24+
- Create an API key in your Local Contexts account.
25+
- Configure the DATAVERSE_LOCALCONTEXTS_URL and DATAVERSE_LOCALCONTEXTS_API_KEY as described in the :ref:`localcontexts` section of the Configuration Guide.
26+
- Add the Local Contexts metadatablock and configure the associated external vocabulary script. Both are available, along with installation instructions, in the `Dataverse External Vocabulary GitHub Repository <https://github.com/gdcc/dataverse-external-vocab-support/blob/main/packages/local_contexts/README.md>`_.
27+
The metadatablock contains one field allowing Dataverse to store the URL of an associated Local Contexts Hub project. Be sure to update the Solr schema after installing the metadatablock (see :ref:`update-solr-schema`).
28+
The external vocabulary script interacts with the Local Contexts Hub (via the Dataverse server) to display the Labels and Notices associated with the proect and provide a link to it.
29+
The script also supports adding/removing such a link from the dataset's metadata. Note that only a project that references the dataset's PID in its `Optional Project Information` field can be linked to a dataset.
30+
- Lastly, to show Local Contexts information in the summary section of the dataset page, as shown in the image above, you should add `LCProjectUrl` to list of custom summary fields via use of the :ref:`:CustomDatasetSummaryFields` setting.
31+
- Optionally, one can also set the dataverse.feature.add-local-contexts-permission-check FeatureFlag to true. This assures that only users editing datasets can use the LocalContexts search functionality (e.g. via API).
32+
This is not recommended unless problematic use is seen.
3533

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import edu.harvard.iq.dataverse.Dataset;
1212
import edu.harvard.iq.dataverse.DatasetServiceBean;
1313
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
14+
import edu.harvard.iq.dataverse.GlobalId;
1415
import edu.harvard.iq.dataverse.PermissionServiceBean;
1516
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
1617
import edu.harvard.iq.dataverse.authorization.Permission;
@@ -21,6 +22,7 @@
2122
import edu.harvard.iq.dataverse.util.SystemConfig;
2223
import jakarta.ejb.EJB;
2324
import jakarta.inject.Inject;
25+
import jakarta.json.JsonArray;
2426
import jakarta.json.JsonObject;
2527
import jakarta.ws.rs.*;
2628
import jakarta.ws.rs.core.Context;
@@ -68,8 +70,8 @@ public Response getDatasetLocalContexts(@Context ContainerRequestContext crc, @P
6870
return error(Response.Status.NOT_FOUND, "LocalContexts API configuration is missing.");
6971
}
7072

71-
String datasetDoi = dataset.getGlobalId().asString();
72-
String apiUrl = localContextsUrl + "api/v2/projects/?publication_doi=" + datasetDoi;
73+
String datasetDoi = dataset.getGlobalId().asRawIdentifier();
74+
String apiUrl = localContextsUrl + "api/v2/projects/?doi=" + datasetDoi;
7375
logger.fine("URL used: " + apiUrl);
7476
try {
7577
HttpClient client = HttpClient.newHttpClient();
@@ -141,18 +143,20 @@ public Response searchLocalContexts(@PathParam("id") String datasetId, @PathPara
141143
// Check if the response contains a "publication_doi" key
142144
if (jsonResponse.containsKey("external_ids")) {
143145
JsonObject externalIds = jsonResponse.getJsonObject("external_ids");
144-
if (externalIds.containsKey("publication_doi")) {
145-
String responseDoi = externalIds.getString("publication_doi");
146-
String datasetDoi = dataset.getGlobalId().asString();
146+
if (externalIds.containsKey("doi")) {
147+
JsonArray responseDois = externalIds.getJsonArray("doi");
148+
GlobalId datasetPid = dataset.getGlobalId();
147149
// Compare the DOI from the response with the dataset's DOI
148-
if (responseDoi.equals(datasetDoi)) {
149-
// Return the JSON response as-is
150-
return ok(jsonResponse);
151-
} else {
152-
// DOI mismatch, return 404
153-
return error(Response.Status.NOT_FOUND,
154-
"LocalContexts information not found for this dataset.");
150+
for (int i = 0; i < responseDois.size(); i++) {
151+
String doiValue = responseDois.getString(i);
152+
if (doiValue.equals(datasetPid.asString()) || doiValue.equals(datasetPid.asURL())) {
153+
// Return the JSON response as-is
154+
return ok(jsonResponse);
155+
}
155156
}
157+
// DOI mismatch, return 404
158+
return error(Response.Status.NOT_FOUND,
159+
"LocalContexts information not found for this dataset.");
156160
} else {
157161
// "publication_doi" key not found in the response, return 404
158162
return error(Response.Status.NOT_FOUND, "Invalid response from Local Contexts API.");

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

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package edu.harvard.iq.dataverse.api;
22

33
import io.restassured.response.Response;
4-
import edu.harvard.iq.dataverse.settings.JvmSettings;
5-
import edu.harvard.iq.dataverse.util.testing.JvmSetting;
6-
import edu.harvard.iq.dataverse.util.testing.LocalJvmSettings;
74
import org.junit.jupiter.params.ParameterizedTest;
85
import org.junit.jupiter.params.provider.MethodSource;
96

@@ -14,68 +11,11 @@
1411
import java.util.stream.Stream;
1512

1613
/**
17-
* Since testing for valid responses requires seting up a project at
18-
* localcontexts that has the PID of the test dataset and an api key, these
19-
* tests are just checking for various error conditions - if the LocalContexts
20-
* settings aren't set, if the LC server doesn't respond with a 200, if the user
21-
* can't edit the dataset.
14+
* Simple test of NOT_FOUND responses when LocalContexts is not configured
2215
*
2316
*/
24-
@LocalJvmSettings
2517
public class LocalContextsIT {
2618

27-
private static final String RANDOM_API_KEY = "random_api_key_12345";
28-
29-
@ParameterizedTest
30-
@MethodSource("localContextsTestCases")
31-
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_URL, value = "https://localcontexts.org/")
32-
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_API_KEY, value = RANDOM_API_KEY)
33-
public void testGetDatasetLocalContexts(String testCase) {
34-
Response createUser = UtilIT.createRandomUser();
35-
String username = UtilIT.getUsernameFromResponse(createUser);
36-
String apiToken = UtilIT.getApiTokenFromResponse(createUser);
37-
38-
Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
39-
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
40-
41-
Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
42-
Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse);
43-
44-
45-
Response localContextsResponse;
46-
if (testCase.equals("searchLocalContexts")) {
47-
localContextsResponse = UtilIT.searchLocalContexts(datasetId.toString(), apiToken);
48-
} else {
49-
String projectId = "sample_project_id";
50-
localContextsResponse = UtilIT.getLocalContextsProject(datasetId.toString(), projectId, apiToken);
51-
}
52-
localContextsResponse.then().assertThat()
53-
.statusCode(SERVICE_UNAVAILABLE.getStatusCode());
54-
55-
// Test with a second user
56-
Response createSecondUser = UtilIT.createRandomUser();
57-
String secondUsername = UtilIT.getUsernameFromResponse(createSecondUser);
58-
String secondApiToken = UtilIT.getApiTokenFromResponse(createSecondUser);
59-
60-
Response secondUserResponse;
61-
if (testCase.equals("searchLocalContexts")) {
62-
secondUserResponse = UtilIT.searchLocalContexts(datasetId.toString(), secondApiToken);
63-
secondUserResponse.then().assertThat()
64-
.statusCode(FORBIDDEN.getStatusCode());
65-
} else {
66-
String projectId = "sample_project_id";
67-
secondUserResponse = UtilIT.getLocalContextsProject(datasetId.toString(), projectId, secondApiToken);
68-
secondUserResponse.then().assertThat()
69-
.statusCode(SERVICE_UNAVAILABLE.getStatusCode());
70-
}
71-
72-
// Clean up
73-
UtilIT.deleteDatasetViaNativeApi(datasetId, apiToken);
74-
UtilIT.deleteDataverse(dataverseAlias, apiToken);
75-
UtilIT.deleteUser(username);
76-
UtilIT.deleteUser(secondUsername);
77-
}
78-
7919
@ParameterizedTest
8020
@MethodSource("localContextsTestCases")
8121
public void testLocalContextsWithoutConfiguration(String testCase) {
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package edu.harvard.iq.dataverse.api;
2+
3+
import edu.harvard.iq.dataverse.Dataset;
4+
import edu.harvard.iq.dataverse.DatasetServiceBean;
5+
import edu.harvard.iq.dataverse.GlobalId;
6+
import edu.harvard.iq.dataverse.PermissionServiceBean;
7+
import edu.harvard.iq.dataverse.PermissionsWrapper;
8+
import edu.harvard.iq.dataverse.UserServiceBean;
9+
import edu.harvard.iq.dataverse.authorization.Permission;
10+
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
11+
import edu.harvard.iq.dataverse.authorization.users.GuestUser;
12+
import edu.harvard.iq.dataverse.settings.FeatureFlags;
13+
import edu.harvard.iq.dataverse.settings.JvmSettings;
14+
import edu.harvard.iq.dataverse.util.SystemConfig;
15+
import edu.harvard.iq.dataverse.util.testing.JvmSetting;
16+
import edu.harvard.iq.dataverse.util.testing.LocalJvmSettings;
17+
import jakarta.ws.rs.container.ContainerRequestContext;
18+
import jakarta.ws.rs.core.Response;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
import org.mockito.InjectMocks;
22+
import org.mockito.Mock;
23+
import org.mockito.Mockito;
24+
import org.mockito.junit.jupiter.MockitoExtension;
25+
26+
import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
27+
import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
28+
import static jakarta.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE;
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertTrue;
31+
import static org.mockito.Mockito.when;
32+
33+
@LocalJvmSettings
34+
@ExtendWith(MockitoExtension.class)
35+
public class LocalContextsTest {
36+
37+
@InjectMocks
38+
private LocalContexts localContexts;
39+
40+
@Mock
41+
private DatasetServiceBean datasetService;
42+
@Mock
43+
private UserServiceBean userService;
44+
@Mock
45+
private SystemConfig systemConfig;
46+
@Mock
47+
private PermissionsWrapper permissionsWrapper;
48+
@Mock
49+
private PermissionServiceBean permissionService;
50+
@Mock
51+
private AuthenticatedUser authUser;
52+
@Mock
53+
private Dataset dataset;
54+
@Mock
55+
private ContainerRequestContext crc;
56+
57+
private static final String RANDOM_API_KEY = "random_api_key_12345";
58+
59+
@Test
60+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_URL, value = "https://sandbox.localcontextshub.org/")
61+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_API_KEY, value = RANDOM_API_KEY)
62+
public void testSearchLocalContexts_ServiceUnavailable() {
63+
// given
64+
when(datasetService.find(1L)).thenReturn(dataset);
65+
// No mock for http client, so it will fail and return SERVICE_UNAVAILABLE
66+
67+
// when
68+
Response response = localContexts.searchLocalContexts("1", "2");
69+
70+
// then
71+
assertEquals(SERVICE_UNAVAILABLE.getStatusCode(), response.getStatus());
72+
}
73+
74+
@Test
75+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_URL, value = "https://sandbox.localcontextshub.org/")
76+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_API_KEY, value = RANDOM_API_KEY)
77+
public void testGetLocalContextsProject_ServiceUnavailable() {
78+
// given
79+
when(datasetService.find(1L)).thenReturn(dataset);
80+
when(dataset.getGlobalId()).thenReturn(new GlobalId("doi", "10.5072", "FK2ABCDEF", "/","https://doi.org/", "test"));
81+
when(crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER))
82+
.thenReturn(GuestUser.get());
83+
84+
// No mock for http client, so it will fail and return SERVICE_UNAVAILABLE
85+
86+
// when
87+
Response response = localContexts.getDatasetLocalContexts(crc ,"1");
88+
89+
// then
90+
assertEquals(SERVICE_UNAVAILABLE.getStatusCode(), response.getStatus());
91+
}
92+
93+
@Test
94+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_URL, value = "https://sandbox.localcontextshub.org/")
95+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_API_KEY, value = RANDOM_API_KEY)
96+
@JvmSetting(key = JvmSettings.FEATURE_FLAG, value = "true", varArgs = "add-local-contexts-permission-check")
97+
public void testGetLocalContextsProject_Forbidden() {
98+
assertTrue(FeatureFlags.ADD_LOCAL_CONTEXTS_PERMISSION_CHECK.enabled());
99+
// given
100+
AuthenticatedUser au = new AuthenticatedUser();
101+
au.setId(1L);
102+
when(datasetService.find(1L)).thenReturn(dataset);
103+
when(crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER))
104+
.thenReturn(au);
105+
PermissionServiceBean.StaticPermissionQuery staticPermissionQuery = Mockito.mock(PermissionServiceBean.StaticPermissionQuery.class);
106+
when(permissionService.userOn(Mockito.any(AuthenticatedUser.class), Mockito.any(Dataset.class))).thenReturn(staticPermissionQuery);
107+
when(staticPermissionQuery.has(Permission.EditDataset)).thenReturn(false);
108+
109+
// when
110+
Response response = localContexts.getDatasetLocalContexts(crc, "1");
111+
112+
// then
113+
assertEquals(FORBIDDEN.getStatusCode(), response.getStatus());
114+
}
115+
116+
@Test
117+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_URL, value = "https://sandbox.localcontextshub.org/")
118+
@JvmSetting(key = JvmSettings.LOCALCONTEXTS_API_KEY, value = RANDOM_API_KEY)
119+
@JvmSetting(key = JvmSettings.FEATURE_FLAG, value = "true", varArgs = "add-local-contexts-permission-check")
120+
public void testGetLocalContextsProject_Allowed() {
121+
assertTrue(FeatureFlags.ADD_LOCAL_CONTEXTS_PERMISSION_CHECK.enabled());
122+
// given
123+
AuthenticatedUser au = new AuthenticatedUser();
124+
au.setId(1L);
125+
when(datasetService.find(1L)).thenReturn(dataset);
126+
when(dataset.getGlobalId()).thenReturn(new GlobalId("doi", "10.5072", "FK2ABCDEF", "/","https://doi.org/", "test"));
127+
when(crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER))
128+
.thenReturn(au);
129+
PermissionServiceBean.StaticPermissionQuery staticPermissionQuery = Mockito.mock(PermissionServiceBean.StaticPermissionQuery.class);
130+
when(permissionService.userOn(Mockito.any(AuthenticatedUser.class), Mockito.any(Dataset.class))).thenReturn(staticPermissionQuery);
131+
when(staticPermissionQuery.has(Permission.EditDataset)).thenReturn(true);
132+
133+
// when
134+
Response response = localContexts.getDatasetLocalContexts(crc, "1");
135+
136+
// then
137+
assertEquals(SERVICE_UNAVAILABLE.getStatusCode(), response.getStatus());
138+
}
139+
140+
@Test
141+
public void testSearchLocalContexts_NoConfig() {
142+
143+
// when
144+
Response response = localContexts.searchLocalContexts("1", "2");
145+
146+
// then
147+
assertEquals(NOT_FOUND.getStatusCode(), response.getStatus());
148+
}
149+
150+
@Test
151+
public void testGetLocalContextsProject_NoConfig() {
152+
153+
when(datasetService.find(1L)).thenReturn(dataset);
154+
when(crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER))
155+
.thenReturn(GuestUser.get());
156+
// when
157+
Response response = localContexts.getDatasetLocalContexts(crc, "1");
158+
159+
// then
160+
assertEquals(NOT_FOUND.getStatusCode(), response.getStatus());
161+
}
162+
}

0 commit comments

Comments
 (0)