Skip to content

Commit 5781291

Browse files
authored
Merge pull request #11294 from GlobalDataverseCommunityConsortium/TKLabels
TKLabels v2 implementation
2 parents 996a58b + 61181ba commit 5781291

File tree

11 files changed

+411
-1
lines changed

11 files changed

+411
-1
lines changed

doc/release-notes/TKLabels.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
New API calls to find projects at https://localcontextshub.org associated with a dataset have been added. This supports integration via
2+
an external vocabulary script that allows users to associate such a project with their dataset and display the associated Notices and Tribal Knowledge Labels.
3+
4+
5+
Connecting to LocalContexts requires a LocalContexts API Key. Using both the production and sandbox (test) LocalContexts servers are supported.
6+
7+
See also [the guides](https://dataverse-guide--11294.org.readthedocs.build/en/11294/installation/localcontexts.html) and #11294.
8+
9+
## Settings Added
10+
11+
The following settings have been added:
12+
13+
dataverse.localcontexts.url
14+
dataverse.localcontexts.api-key
15+
16+
17+
18+
19+

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3594,6 +3594,31 @@ This setting allows admins to highlight a few of the 1000+ CSL citation styles a
35943594
These will be listed above the alphabetical list of all styles in the "View Styled Citations" pop-up.
35953595
The default value when not set is "chicago-author-date, ieee".
35963596

3597+
.. _localcontexts:
3598+
3599+
localcontexts.url
3600+
+++++++++++++++++
3601+
3602+
.. note::
3603+
For more information about LocalContexts integration, see :doc:`/installation/localcontexts`.
3604+
3605+
The URL for the Local Contexts Hub API.
3606+
3607+
| Example: ``https://localcontextshub.org/``
3608+
| The sandbox URL ``https://sandbox.localcontextshub.org/`` can be used for testing.
3609+
3610+
Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_LOCALCONTEXTS_URL``.
3611+
3612+
localcontexts.api-key
3613+
+++++++++++++++++++++
3614+
3615+
The API key for accessing the Local Contexts Hub.
3616+
3617+
| Example: ``your_api_key_here``
3618+
| It's recommended to use a password alias for this setting, as described in the :ref:`secure-password-storage` section.
3619+
3620+
Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_LOCALCONTEXTS_API_KEY``.
3621+
35973622
.. _dataverse.cors:
35983623

35993624
CORS Settings
63.7 KB
Loading

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ Installation Guide
2222
oidc
2323
orcid
2424
external-tools
25+
localcontexts
2526
advanced
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
LocalContexts Integration
2+
=========================
3+
4+
.. contents:: |toctitle|
5+
:local:
6+
7+
`Local Contexts <https://localcontexts.org/>`_ is a global initiative that supports Indigenous communities in the management and sharing of their cultural heritage and data.
8+
The `Local Contexts Hub <https://localcontextshub.org/>`_ is a platform that enables the creation and application of Traditional Knowledge (TK) and Biocultural (BC) Labels and Notices.
9+
These labels and notices help to communicate the cultural context and appropriate use of Indigenous data and cultural heritage materials.
10+
11+
Dataverse supports integration with the Local Contexts Hub so that Labels and Notices associated with a dataset can be displayed on the dataset page:
12+
13+
.. figure:: ./img/LCDemo.png
14+
:alt: Dataset Page showing Local Contexts integration with Dataverse Software
15+
16+
Configuration
17+
-------------
18+
19+
There are several steps to LocalContexts integration.
20+
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.)
35+

doc/sphinx-guides/source/user/appendix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Unlike supported metadata, experimental metadata is not enabled by default in a
4040
- `CodeMeta Software Metadata <https://docs.google.com/spreadsheets/d/e/2PACX-1vTE-aSW0J7UQ0prYq8rP_P_AWVtqhyv46aJu9uPszpa9_UuOWRsyFjbWFDnCd7us7PSIpW7Qg2KwZ8v/pub>`__: based on the `CodeMeta Software Metadata Schema, version 2.0 <https://codemeta.github.io/terms/>`__ (`see .tsv version <https://github.com/IQSS/dataverse/blob/master/scripts/api/data/metadatablocks/codemeta.tsv>`__)
4141
- Computational Workflow Metadata (`see .tsv <https://github.com/IQSS/dataverse/blob/master/scripts/api/data/metadatablocks/computational_workflow.tsv>`__): adapted from `Bioschemas Computational Workflow Profile, version 1.0 <https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE>`__ and `Codemeta <https://codemeta.github.io/terms/>`__.
4242
- Archival Metadata (`see .tsv <https://github.com/IQSS/dataverse/blob/master/scripts/api/data/metadatablocks/archival.tsv>`__): Enables repositories to register metadata relating to the potential archiving of the dataset at a depositor archive, whether that be your own institutional archive or an external archive, i.e. a historical archive.
43+
- Local Contexts Metadata (`see .tsv <https://github.com/gdcc/dataverse-external-vocab-support/blob/main/packages/local_contexts/cvocLocalContexts.tsv>`__): Supports integration with the `Local Contexts <https://localcontexts.org/>`__ platform, enabling the use of Traditional Knowledge and Biocultural Labels, and Notices. For more information on setup and configuration, see :doc:`../installation/localcontexts`.
4344

4445
Please note: these custom metadata schemas are not included in the Solr schema for indexing by default, you will need
4546
to add them as necessary for your custom metadata blocks. See "Update the Solr Schema" in :doc:`../admin/metadatacustomization`.
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package edu.harvard.iq.dataverse.api;
2+
3+
import java.io.IOException;
4+
import java.net.URI;
5+
import java.net.URISyntaxException;
6+
import java.net.http.HttpClient;
7+
import java.net.http.HttpRequest;
8+
import java.net.http.HttpResponse;
9+
import java.util.logging.Logger;
10+
11+
import edu.harvard.iq.dataverse.Dataset;
12+
import edu.harvard.iq.dataverse.DatasetServiceBean;
13+
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
14+
import edu.harvard.iq.dataverse.PermissionServiceBean;
15+
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
16+
import edu.harvard.iq.dataverse.authorization.Permission;
17+
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
18+
import edu.harvard.iq.dataverse.settings.FeatureFlags;
19+
import edu.harvard.iq.dataverse.settings.JvmSettings;
20+
import edu.harvard.iq.dataverse.util.json.JsonUtil;
21+
import jakarta.ejb.EJB;
22+
import jakarta.inject.Inject;
23+
import jakarta.json.JsonObject;
24+
import jakarta.ws.rs.*;
25+
import jakarta.ws.rs.core.Context;
26+
import jakarta.ws.rs.core.MediaType;
27+
import jakarta.ws.rs.core.Response;
28+
import jakarta.ws.rs.container.ContainerRequestContext;
29+
30+
@Path("localcontexts")
31+
public class LocalContexts extends AbstractApiBean {
32+
33+
protected static final Logger logger = Logger.getLogger(LocalContexts.class.getName());
34+
35+
@EJB
36+
DatasetServiceBean datasetService;
37+
38+
@Inject
39+
DataverseRequestServiceBean dvRequestService;
40+
41+
@EJB
42+
PermissionServiceBean permissionService;
43+
44+
@GET
45+
@Path("/datasets/{id}")
46+
@Produces(MediaType.APPLICATION_JSON)
47+
@AuthRequired
48+
public Response getDatasetLocalContexts(@Context ContainerRequestContext crc, @PathParam("id") String id) {
49+
try {
50+
Dataset dataset = findDatasetOrDie(id);
51+
DataverseRequest req = createDataverseRequest(getRequestUser(crc));
52+
53+
// Check if the user has edit dataset permission
54+
/* Feature flag to skip permission check
55+
* If you add the api-session-auth FeatureFlag, you can verify if the user has edit permissions
56+
*
57+
*/
58+
if (FeatureFlags.ADD_LOCAL_CONTEXTS_PERMISSION_CHECK.enabled() && !permissionService.userOn(req.getUser(), dataset).has(Permission.EditDataset)) {
59+
return error(Response.Status.FORBIDDEN,
60+
"You do not have permission to query LocalContexts about this dataset.");
61+
}
62+
63+
String localContextsUrl = JvmSettings.LOCALCONTEXTS_URL.lookupOptional().orElse(null);
64+
String localContextsApiKey = JvmSettings.LOCALCONTEXTS_API_KEY.lookupOptional().orElse(null);
65+
66+
if (localContextsUrl == null || localContextsApiKey == null) {
67+
return error(Response.Status.NOT_FOUND, "LocalContexts API configuration is missing.");
68+
}
69+
70+
String datasetDoi = dataset.getGlobalId().asString();
71+
String apiUrl = localContextsUrl + "api/v2/projects/?publication_doi=" + datasetDoi;
72+
logger.fine("URL used: " + apiUrl);
73+
try {
74+
HttpClient client = HttpClient.newHttpClient();
75+
HttpRequest request = HttpRequest.newBuilder().uri(new URI(apiUrl))
76+
.header("X-Api-Key", localContextsApiKey).GET().build();
77+
78+
HttpResponse<String> response;
79+
80+
response = client.send(request, HttpResponse.BodyHandlers.ofString());
81+
82+
if (response.statusCode() == 200) {
83+
// Assuming the response is already in JSON format
84+
logger.fine("Response from search: " + response.body());
85+
JsonObject jsonObject = JsonUtil.getJsonObject(response.body());
86+
return ok(jsonObject);
87+
} else {
88+
return error(Response.Status.SERVICE_UNAVAILABLE,
89+
"Error from LocalContexts API: " + response.statusCode());
90+
}
91+
} catch (URISyntaxException e) {
92+
logger.warning(e.getMessage());
93+
return error(Response.Status.SERVICE_UNAVAILABLE, "LocalContexts connection misconfigured.");
94+
} catch (IOException | InterruptedException e) {
95+
logger.warning(e.getMessage());
96+
e.printStackTrace();
97+
return error(Response.Status.SERVICE_UNAVAILABLE, "Error contacting LocalContexts");
98+
99+
}
100+
} catch (WrappedResponse ex) {
101+
return ex.getResponse();
102+
}
103+
}
104+
105+
@GET
106+
@Path("/datasets/{id}/{projectId}")
107+
@Produces(MediaType.APPLICATION_JSON)
108+
public Response searchLocalContexts(@PathParam("id") String datasetId, @PathParam("projectId") String projectId) {
109+
try {
110+
Dataset dataset = findDatasetOrDie(datasetId);
111+
String localContextsUrl = JvmSettings.LOCALCONTEXTS_URL.lookupOptional().orElse(null);
112+
String localContextsApiKey = JvmSettings.LOCALCONTEXTS_API_KEY.lookupOptional().orElse(null);
113+
114+
if (localContextsUrl == null || localContextsApiKey == null) {
115+
return error(Response.Status.NOT_FOUND, "LocalContexts API configuration is missing.");
116+
}
117+
118+
String apiUrl = localContextsUrl + "api/v2/projects/" + projectId + "/";
119+
logger.fine("URL used: " + apiUrl);
120+
try {
121+
HttpClient client = HttpClient.newHttpClient();
122+
HttpRequest request = HttpRequest.newBuilder().uri(new URI(apiUrl))
123+
.header("X-Api-Key", localContextsApiKey).GET().build();
124+
125+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
126+
127+
if (response.statusCode() == 200) {
128+
// Parse the JSON response
129+
JsonObject jsonResponse = JsonUtil.getJsonObject(response.body());
130+
logger.fine("Response from get: " + JsonUtil.prettyPrint(jsonResponse));
131+
132+
// Check if the response contains a "publication_doi" key
133+
if (jsonResponse.containsKey("external_ids")) {
134+
JsonObject externalIds = jsonResponse.getJsonObject("external_ids");
135+
if (externalIds.containsKey("publication_doi")) {
136+
String responseDoi = externalIds.getString("publication_doi");
137+
String datasetDoi = dataset.getGlobalId().asString();
138+
// Compare the DOI from the response with the dataset's DOI
139+
if (responseDoi.equals(datasetDoi)) {
140+
// Return the JSON response as-is
141+
return ok(jsonResponse);
142+
} else {
143+
// DOI mismatch, return 404
144+
return error(Response.Status.NOT_FOUND,
145+
"LocalContexts information not found for this dataset.");
146+
}
147+
} else {
148+
// "publication_doi" key not found in the response, return 404
149+
return error(Response.Status.NOT_FOUND, "Invalid response from Local Contexts API.");
150+
}
151+
} else {
152+
// "external_ids" key not found in the response, return 404
153+
return error(Response.Status.NOT_FOUND, "Invalid response from Local Contexts API.");
154+
}
155+
156+
} else {
157+
return error(Response.Status.SERVICE_UNAVAILABLE,
158+
"Error from Local Contexts API: " + response.statusCode());
159+
}
160+
} catch (URISyntaxException e) {
161+
logger.warning(e.getMessage());
162+
return error(Response.Status.SERVICE_UNAVAILABLE, "LocalContexts connection misconfigured.");
163+
} catch (IOException | InterruptedException e) {
164+
logger.warning(e.getMessage());
165+
e.printStackTrace();
166+
return error(Response.Status.SERVICE_UNAVAILABLE, "Error contacting LocalContexts");
167+
}
168+
} catch (WrappedResponse ex) {
169+
return ex.getResponse();
170+
}
171+
}
172+
}

src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
public enum FeatureFlags {
2626

2727
/**
28-
* Enables API authentication via session cookie (JSESSIONID). Caution: Enabling this feature flag exposes the installation to CSRF risks
28+
* Enables API authentication via session cookie (JSESSIONID). Caution: Enabling this feature flag may expose the installation to CSRF risks
2929
* @apiNote Raise flag by setting "dataverse.feature.api-session-auth"
3030
* @since Dataverse 5.14
3131
*/
@@ -151,6 +151,21 @@ public enum FeatureFlags {
151151
* @since Dataverse 6.5
152152
*/
153153
VERSION_NOTE("enable-version-note"),
154+
/**
155+
* This flag adds a permission check to assure that the user calling the
156+
* /api/localcontexts/datasets/{id} can edit the dataset with that id. This is
157+
* currently the only use case - see
158+
* https://github.com/gdcc/dataverse-external-vocab-support/tree/main/packages/local_contexts.
159+
* The flag adds additional security to stop other uses, but would currently
160+
* have to be used in conjunction with the api-session-auth feature flag (the
161+
* security implications of which have not been fully investigated) to still
162+
* allow adding Local Contexts metadata to a dataset.
163+
*
164+
* @apiNote Raise flag by setting
165+
* "dataverse.feature.add-local-contexts-permission-check"
166+
* @since Dataverse 6.5
167+
*/
168+
ADD_LOCAL_CONTEXTS_PERMISSION_CHECK("add-local-contexts-permission-check"),
154169

155170
;
156171

src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ public enum JvmSettings {
281281
SCOPE_CORS_HEADERS(SCOPE_CORS, "headers"),
282282
CORS_ALLOW_HEADERS(SCOPE_CORS_HEADERS, "allow"),
283283
CORS_EXPOSE_HEADERS(SCOPE_CORS_HEADERS, "expose"),
284+
285+
// LOCALCONTEXTS
286+
SCOPE_LOCALCONTEXTS(PREFIX, "localcontexts"),
287+
LOCALCONTEXTS_URL(SCOPE_LOCALCONTEXTS, "url"),
288+
LOCALCONTEXTS_API_KEY(SCOPE_LOCALCONTEXTS, "api-key"),
284289
;
285290

286291
private static final String SCOPE_SEPARATOR = ".";

0 commit comments

Comments
 (0)