Skip to content

Commit b01ffe4

Browse files
authored
Merge pull request #11760 from GlobalDataverseCommunityConsortium/SPA-_api_to_getToolLaunchUrl
Add API to get tool launch url for SPA
2 parents 838be81 + 2b9bab9 commit b01ffe4

File tree

12 files changed

+761
-244
lines changed

12 files changed

+761
-244
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
New API calls have been added to retrieve the URLs needed to launch external tools on specific datasets and files:
2+
3+
/api/datasets/$DATASET_ID/externalTool/$TOOL_ID/toolUrl
4+
and
5+
/api/files/$FILE_ID/externalTool/$TOOL_ID/toolUrl
6+
7+
If the dataset/file is not public, the caller must authenticate and have permission to view the dataset/file. In such cases, the generated URL will include a callback token containing a signed URL the tool can use to retrieve all the parameters it is configured for.
8+
9+
Backward incompatibility:
10+
The responses from the GET /api/externalTools and /api/externalTools/{id} are now formatted as JSON (previously the toolParameters and allowedApiCalls were JSON serialized as strings) and any confiugured "requirements" are included.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ v6.8
1313
- For POST /api/files/{id}/metadata passing an empty string ("description":"") or array ("categories":[]) will no longer be ignored. Empty fields will now clear out the values in the file's metadata. To ignore the fields simply do not include them in the JSON string.
1414
- For PUT /api/datasets/{id}/editMetadata the query parameter "sourceInternalVersionNumber" has been removed and replaced with "sourceLastUpdateTime" to verify that the data being edited hasn't been modified and isn't stale.
1515
- For GET /api/dataverses/$dataverse-alias/links the Json response has changed breaking the backward compatibility of the API.
16+
- For GET /api/externalTools and /api/externalTools/{id} the responses are now formatted as JSON (previously the toolParameters and allowedApiCalls were a JSON object and array (respectively) that were serialized as JSON strings) and any configured "requirements" are included.
1617

1718
v6.7
1819
----

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

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3442,10 +3442,62 @@ Archiving is an optional feature that may be configured for a Dataverse installa
34423442
34433443
curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/datasets/:persistentId/$VERSION/archivalStatus?persistentId=$PERSISTENT_IDENTIFIER"
34443444
3445+
Get Dataset External Tool URL
3446+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3447+
3448+
This API call generates a URL for accessing an external tool (see :doc:`/installation/external-tools`) that operates at the dataset level. The URL includes necessary authentication tokens and parameters based on the user's permissions and the tool's configuration.
3449+
3450+
Authentication is required for draft or deaccessioned datasets and the user must have ViewUnpublishedDataset permission.
3451+
3452+
.. code-block:: bash
3453+
3454+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3455+
export SERVER_URL=https://demo.dataverse.org
3456+
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/7U7YBV
3457+
export TOOL_ID=42
3458+
3459+
curl -H "X-Dataverse-key:$API_TOKEN" -X POST "$SERVER_URL/api/datasets/:persistentId/externalTool/$TOOL_ID/toolUrl?persistentId=$PERSISTENT_IDENTIFIER" \
3460+
-H "Content-Type: application/json" \
3461+
-d '{"preview": false, "locale": "en"}'
3462+
3463+
The fully expanded example above (without environment variables) looks like this:
3464+
3465+
.. code-block:: bash
3466+
3467+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/:persistentId/externalTool/42/toolUrl?persistentId=doi:10.5072/FK2/7U7YBV" \
3468+
-H "Content-Type: application/json" \
3469+
-d '{"preview": false, "locale": "en"}'
3470+
3471+
The JSON request body accepts the following optional parameters:
3472+
3473+
- ``preview``: boolean flag to indicate if the tool should run in preview mode (default: false)
3474+
- ``locale``: string specifying the locale for internationalization
3475+
3476+
The response includes:
3477+
3478+
- ``toolUrl``: the URL to access the external tool
3479+
- ``toolName``: the display name of the external tool
3480+
- ``datasetId``: the ID of the dataset
3481+
- ``preview``: whether the URL is for preview mode
3482+
3483+
Example response:
3484+
3485+
.. code-block:: json
3486+
3487+
{
3488+
"status": "OK",
3489+
"data": {
3490+
"toolUrl": "https://example.com/tool?datasetId=1234&callback=ahr...",
3491+
"toolName": "My External Tool",
3492+
"datasetId": 1234,
3493+
"preview": false
3494+
}
3495+
}
3496+
34453497
Get External Tool Parameters
34463498
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34473499

3448-
This API call is intended as a callback that can be used by :doc:`/installation/external-tools` to retrieve signed Urls necessary for their interaction with Dataverse.
3500+
This API call is intended as a callback that can be used by :doc:`/installation/external-tools` to retrieve signed URLs necessary for their interaction with Dataverse.
34493501
It can be called directly as well.
34503502

34513503
The response is a JSON object described in the :doc:`/api/external-tools` section of the API guide.
@@ -5281,6 +5333,82 @@ Note the optional "limit" parameter. Without it, the API will attempt to populat
52815333
52825334
By default, the admin API calls are blocked and can only be called from localhost. See more details in :ref:`:BlockedApiEndpoints <:BlockedApiEndpoints>` and :ref:`:BlockedApiPolicy <:BlockedApiPolicy>` settings in :doc:`/installation/config`.
52835335
5336+
Get File External Tool URL
5337+
~~~~~~~~~~~~~~~~~~~~~~~~~~
5338+
5339+
This API call generates a URL for accessing an external tool (see :doc:`/installation/external-tools`) that operates at the file level. The URL includes necessary authentication tokens and parameters based on the user's permissions and the tool's configuration.
5340+
5341+
Authentication is required for draft, restricted, embargoed, or expired (retention period) files; the user must have appropriate permissions.
5342+
5343+
.. code-block:: bash
5344+
5345+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
5346+
export SERVER_URL=https://demo.dataverse.org
5347+
export FILE_ID=42
5348+
export TOOL_ID=3
5349+
5350+
curl -H "X-Dataverse-key:$API_TOKEN" -X POST "$SERVER_URL/api/files/$FILE_ID/externalTool/$TOOL_ID/toolUrl" \
5351+
-H "Content-Type: application/json" \
5352+
-d '{"preview": false, "locale": "en"}'
5353+
5354+
The fully expanded example above (without environment variables) looks like this:
5355+
5356+
.. code-block:: bash
5357+
5358+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/files/42/externalTool/3/toolUrl" \
5359+
-H "Content-Type: application/json" \
5360+
-d '{"preview": false, "locale": "en"}'
5361+
5362+
The JSON request body accepts the following optional parameters:
5363+
5364+
- ``preview``: boolean flag to indicate if the tool should run in preview mode (default: false)
5365+
- ``locale``: string specifying the locale for internationalization
5366+
5367+
The response includes:
5368+
5369+
- ``toolUrl``: the URL to access the external tool
5370+
- ``toolName``: the display name of the external tool
5371+
- ``fileId``: the ID of the file
5372+
- ``preview``: whether the URL is for preview mode
5373+
5374+
Example response:
5375+
5376+
.. code-block:: json
5377+
5378+
{
5379+
"status": "OK",
5380+
"data": {
5381+
"toolUrl": "https://example.com/tool?fileId=42&callback=ahr...",
5382+
"toolName": "File Viewer Tool",
5383+
"fileId": 42,
5384+
"preview": false
5385+
}
5386+
}
5387+
5388+
Get File External Tool Parameters
5389+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5390+
5391+
This API call is intended as a callback that can be used by :doc:`/installation/external-tools` to retrieve signed URLs necessary for their interaction with Dataverse files.
5392+
It can be called directly as well.
5393+
5394+
The response is a JSON object described in the :doc:`/api/external-tools` section of the API guide.
5395+
5396+
.. code-block:: bash
5397+
5398+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
5399+
export SERVER_URL=https://demo.dataverse.org
5400+
export FILE_ID=42
5401+
export FILE_METADATA_ID=123
5402+
export TOOL_ID=3
5403+
5404+
curl -H "X-Dataverse-key: $API_TOKEN" -H "Accept:application/json" "$SERVER_URL/api/files/$FILE_ID/metadata/$FILE_METADATA_ID/toolparams/$TOOL_ID"
5405+
5406+
The fully expanded example above (without environment variables) looks like this:
5407+
5408+
.. code-block:: bash
5409+
5410+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept:application/json" "https://demo.dataverse.org/api/files/42/metadata/123/toolparams/3"
5411+
52845412
Get External Tool Parameters
52855413
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52865414

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,8 +1138,10 @@ public void setSelectedTool(ExternalTool selectedTool) {
11381138
public String preview(ExternalTool externalTool) {
11391139
ApiToken apiToken = null;
11401140
User user = session.getUser();
1141-
if (fileMetadata.getDatasetVersion().isDraft() || fileMetadata.getDatasetVersion().isDeaccessioned() || (fileMetadata.getDataFile().isRestricted()) || (FileUtil.isActivelyEmbargoed(fileMetadata))) {
1142-
apiToken=authService.getValidApiTokenForUser(user);
1141+
if (fileMetadata.getDatasetVersion().isDraft() || (fileMetadata.getDataFile().isRestricted())
1142+
|| fileMetadata.getDatasetVersion().isDeaccessioned() || (FileUtil.isActivelyEmbargoed(fileMetadata))
1143+
|| (FileUtil.isRetentionExpired(fileMetadata))) {
1144+
apiToken = authService.getValidApiTokenForUser(user);
11431145
}
11441146
if(externalTool == null){
11451147
return "";

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

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5153,6 +5153,129 @@ private boolean isSingleVersionArchiving() {
51535153
return false;
51545154
}
51555155

5156+
/**
5157+
* API endpoint to retrieve a URL for a dataset-level external tool.
5158+
*
5159+
* This endpoint allows clients to get a URL for accessing an external tool
5160+
* that operates at the dataset level. The URL includes necessary authentication tokens and
5161+
* parameters based on the user's permissions and the tool's configuration.
5162+
*
5163+
* The endpoint accepts JSON input with optional parameters:
5164+
* - preview: boolean flag to indicate if the tool should run in preview mode (preview mode, if supported by the tool, suppresses showing metadata (i.e. item name/PID) and is intended for cases where the tool is embedded in the dataset/file page and this metadata is not needed. The current JSF UI never embeds a dataset-level tool in an iframe, so this is param is not currently useful (and may not be supported in dataset tools yet)
5165+
* - locale: string specifying the locale for internationalization
5166+
*
5167+
* The response includes:
5168+
* - toolUrl: the URL to access the external tool
5169+
* - toolName: the display name of the external tool
5170+
* - datasetId: the ID of the dataset
5171+
* - preview: whether the URL is for preview mode
5172+
*
5173+
* Authentication is required, and appropriate permissions are checked before generating the URL.
5174+
* For restricted datasets (draft or deaccessioned), the user must have ViewUnpublishedDataset permission.
5175+
*
5176+
* @param crc The container request context for authentication
5177+
* @param datasetId The ID of the dataset
5178+
* @param externalToolId The ID of the external tool
5179+
* @param jsonBody JSON containing optional parameters
5180+
* @return A Response with the external tool URL and related information
5181+
*/
5182+
@POST
5183+
@AuthRequired
5184+
@Path("{id}/externalTool/{tid}/toolUrl")
5185+
@Consumes(MediaType.APPLICATION_JSON)
5186+
public Response getDatasetExternalToolUrl(@Context ContainerRequestContext crc, @PathParam("id") String datasetId,
5187+
@PathParam("tid") long externalToolId, String jsonBody) {
5188+
5189+
boolean preview = false;
5190+
String locale = null;
5191+
5192+
// Parse request body for parameters
5193+
if (StringUtils.isNotBlank(jsonBody)) {
5194+
try {
5195+
jakarta.json.JsonObject jsonObject = JsonUtil.getJsonObject(jsonBody);
5196+
if (jsonObject.containsKey("preview")) {
5197+
preview = jsonObject.getBoolean("preview");
5198+
}
5199+
if (jsonObject.containsKey("locale")) {
5200+
locale = jsonObject.getString("locale");
5201+
}
5202+
} catch (JsonParsingException | NullPointerException e) {
5203+
logger.warning("Error parsing JSON: " + e.getMessage());
5204+
// Return an error response for malformed JSON
5205+
return error(Response.Status.BAD_REQUEST, "Invalid JSON format in request body");
5206+
}
5207+
}
5208+
5209+
try {
5210+
// Find the dataset
5211+
Dataset dataset;
5212+
try {
5213+
dataset = findDatasetOrDie(datasetId);
5214+
} catch (WrappedResponse ex) {
5215+
return notFound("Dataset not found for given id: " + datasetId);
5216+
}
5217+
5218+
// Find the external tool
5219+
ExternalTool externalTool = externalToolService.findById(externalToolId);
5220+
if (externalTool == null) {
5221+
return error(BAD_REQUEST, "External tool not found with id: " + externalToolId);
5222+
}
5223+
5224+
// Check if the tool has dataset scope
5225+
if (!ExternalTool.Scope.DATASET.equals(externalTool.getScope())) {
5226+
return error(BAD_REQUEST, "External tool does not have dataset scope.");
5227+
}
5228+
5229+
// Get the current user and create a request object
5230+
User user = getRequestUser(crc);
5231+
DataverseRequest req = createDataverseRequest(user);
5232+
5233+
// Get the latest dataset version
5234+
DatasetVersion datasetVersion = dataset.getLatestVersion();
5235+
if (datasetVersion == null) {
5236+
return error(BAD_REQUEST, "Dataset version not found.");
5237+
}
5238+
5239+
// Check if the dataset is restricted or draft
5240+
boolean isRestricted = datasetVersion.isDraft() || datasetVersion.isDeaccessioned();
5241+
5242+
// Check if user has permission to access the dataset if it's restricted
5243+
if (isRestricted) {
5244+
boolean hasPermission = permissionSvc.requestOn(req, dataset).has(Permission.ViewUnpublishedDataset);
5245+
if (!hasPermission) {
5246+
return error(Response.Status.FORBIDDEN,
5247+
"You do not have permission to access this dataset with the requested external tool.");
5248+
}
5249+
}
5250+
5251+
// Determine if we need an API token for authentication
5252+
ApiToken apiToken = null;
5253+
if (user.isAuthenticated() && isRestricted) {
5254+
apiToken = authSvc.getValidApiTokenForUser(user);
5255+
}
5256+
5257+
// Create the external tool handler
5258+
ExternalToolHandler externalToolHandler = new ExternalToolHandler(externalTool, dataset, apiToken, locale);
5259+
5260+
// Get the tool URL
5261+
String toolUrl;
5262+
if (preview) {
5263+
toolUrl = externalToolHandler.getToolUrlForPreviewMode();
5264+
} else {
5265+
toolUrl = externalToolHandler.getToolUrlWithQueryParams();
5266+
}
5267+
5268+
// Return the URL in a JSON response
5269+
return ok(Json.createObjectBuilder().add("toolUrl", toolUrl).add("displayName", externalTool.getDisplayName())
5270+
.add("datasetId", dataset.getId()).add("preview", preview));
5271+
5272+
} catch (Exception ex) {
5273+
logger.log(Level.SEVERE, "Error getting dataset external tool URL: " + ex.getMessage(), ex);
5274+
return error(Response.Status.INTERNAL_SERVER_ERROR,
5275+
"An error occurred while generating the external tool URL.");
5276+
}
5277+
}
5278+
51565279
// This method provides a callback for an external tool to retrieve it's
51575280
// parameters/api URLs. If the request is authenticated, e.g. by it being
51585281
// signed, the api URLs will be signed. If a guest request is made, the URLs

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class ExternalToolsApi extends AbstractApiBean {
2020

2121
@GET
2222
public Response getExternalTools() {
23+
//ToDo - allow filtering by scope, tool type, file content type, etc?
2324
return externalTools.getExternalTools();
2425
}
2526

0 commit comments

Comments
 (0)