Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb64cde
initial guestbook apis
stevenwinship Jan 22, 2026
1c93ba6
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Jan 22, 2026
789ed94
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Jan 27, 2026
7113b48
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Jan 28, 2026
38d67d4
adding guestbook response
stevenwinship Jan 28, 2026
1a1fead
validating response
stevenwinship Jan 28, 2026
6d16aae
validating response
stevenwinship Jan 28, 2026
411befd
Potential fix for code scanning alert no. 354: Information exposure t…
stevenwinship Jan 28, 2026
4833dd8
Potential fix for code scanning alert no. 355: Information exposure t…
stevenwinship Jan 28, 2026
bbea198
Potential fix for code scanning alert no. 356: Information exposure t…
stevenwinship Jan 28, 2026
2e9dd37
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Jan 28, 2026
1a73f06
code cleanup
stevenwinship Jan 28, 2026
b52b856
add -Ddataverse.files.guestbook-at-request=true for testing
stevenwinship Jan 29, 2026
6bbda52
fix test
stevenwinship Jan 30, 2026
969536a
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Jan 30, 2026
2b9c718
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Feb 4, 2026
b73b21e
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Feb 5, 2026
a630c9c
adding post for download datafile with guestbook response
stevenwinship Feb 6, 2026
9d68d33
fix
stevenwinship Feb 6, 2026
79c3eaa
add release note
stevenwinship Feb 9, 2026
b089324
new api and updated docs
stevenwinship Feb 9, 2026
7ab9c0a
updated docs
stevenwinship Feb 9, 2026
b6ec2ea
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Feb 9, 2026
13396f1
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Feb 9, 2026
6604ce5
Merge branch 'develop' into 12001-api-support-termofuse-guestbook
stevenwinship Feb 11, 2026
edf0a6e
refactor and add gb response checks to all download apis
stevenwinship Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions doc/release-notes/12001-api-support-termofuse-guestbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Feature Request: API to support Download Terms of Use and Guestbook

## New Endpoints to download a file or files that required a Guestbook response: POST
A post to these endpoints with the body containing a JSON Guestbook Response will save the response and
`?signed=true`: return a signed URL to download the file(s) or
`?signed=false` or missing: Write the guestbook responses and download the file(s)

`/api/access/datafile/{fileId:.+}`
`/api/access/datafiles/{fileIds}`
`/api/access/dataset/{id}`
`/api/access/dataset/{id}/versions/{versionId}`

A post to these endpoints with the body containing a JSON Guestbook Response will save the response before continuing the download.
No signed URL option exists.
`/api/access/datafiles`
`/api/access/datafile/bundle/{fileId}` POST returns BundleDownloadInstance after processing guestbook responses from body.

## New CRUD Endpoints for Guestbook:
Create a Guestbook: POST `/api/guestbooks/{dataverseIdentifier}`
Get a Guestbook: GET `/api/guestbooks/{id}`
Get a list of Guestbooks linked to a Dataverse Collection: GET `/api/guestbooks/{dataverseIdentifier}/list`
Enable/Disable a Guestbook: PUT `/api/guestbooks/{dataverseIdentifier}/{id}/enabled` Body: `true` or `false`
Note: There is no Update or Delete at this time. You can disable a Guestbook and create a new one.

## For Guestbook At Request:
When JVM setting -Ddataverse.files.guestbook-at-request=true is used a request for access may require a Guestbook response.
PUT `/api/access/datafile/{id}/requestAccess` will now take a JSON Guestbook response in the body.
9 changes: 8 additions & 1 deletion doc/sphinx-guides/source/api/dataaccess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ Basic access URI:

GET http://$SERVER/api/access/datafile/:persistentId?persistentId=doi:10.5072/FK2/J8SJZB

.. note:: Restricted files that require a Guestbook response will require an additional step to supply the response. A POST to the same endpoint with the Guestbook Response in the body will return a signed url that can be used to download the file.

Example ::

POST http://$SERVER/api/access/datafile/:persistentId?persistentId=doi:10.5072/FK2/J8SJZB -d '{"guestbookResponse": {"answers": [{"id": 123,"value": "Good"},{"id": 124,"value": ["Multi","Line"]},{"id": 125,"value": "Yellow"}]}}'

Parameters:
~~~~~~~~~~~
Expand Down Expand Up @@ -361,7 +366,9 @@ This method requests access to the datafile whose id is passed on the behalf of
A curl example using an ``id``::

curl -H "X-Dataverse-key:$API_TOKEN" -X PUT http://$SERVER/api/access/datafile/{id}/requestAccess


.. note:: Some installations of Dataverse may require you to provide a Guestbook response when requesting access to certain restricted files. The response can be passed in the body of this call. See "Get a Guestbook for a Dataverse Collection" in the :doc:`native-api`.

Grant File Access:
~~~~~~~~~~~~~~~~~~

Expand Down
94 changes: 94 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,100 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/guestbookResponses?guestbookId=1" -o myResponses.csv

.. _guestbook-api:

Guestbooks
~~~~~~~~~~

Create a Guestbook for a Dataverse Collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.

Create a Guestbook that can be selected for a Dataset.
You must have "EditDataverse" permission on the Dataverse collection.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=root
export JSON='{"name": "my test guestbook","enabled": true,"emailRequired": true,"nameRequired": true,"institutionRequired": false,"positionRequired": false,"customQuestions": [{"question": "how is your day","required": true,"displayOrder": 0,"type": "text","hidden": false}]}'

curl -POST -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{ID}" -d "$JSON"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -POST -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/root" -d '{"name": "my test guestbook","enabled": true,"emailRequired": true,"nameRequired": true,"institutionRequired": false,"positionRequired": false}'

Get a list of Guestbooks for a Dataverse Collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.

Get a list of Guestbooks for a Dataverse Collection
You must have "EditDataverse" permission on the Dataverse collection.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{ID}/list"`

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/root/list"

Get a Guestbook for a Dataverse Collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.

Get a Guestbook by it's id

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=1234

curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{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" "https://demo.dataverse.org/api/guestbooks/1234"

Enable or Disable a Guestbook for a Dataverse Collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.

Use this endpoint to enable or disable the Guestbook. A Guestbook can not be deleted or modified since there may be responses linked to it.
You must have "EditDataverse" permission on the Dataverse collection.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export dataverseIdentifier=root
export ID=1234

curl -X PUT -d 'true' -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{dataverseIdentifier}/{ID}/enabled"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -X PUT -d 'true' -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/root/1234"

.. _collection-attributes-api:

Change Collection Attributes
Expand Down
1 change: 1 addition & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ services:
-Ddataverse.pid.fake.label=FakeDOIProvider
-Ddataverse.pid.fake.authority=10.5072
-Ddataverse.pid.fake.shoulder=FK2/
#-Ddataverse.files.guestbook-at-request=true
#-Ddataverse.lang.directory=/dv/lang
ports:
- "8080:8080" # HTTP (Dataverse Application)
Expand Down
17 changes: 17 additions & 0 deletions scripts/api/data/guestbook-test-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{"guestbookResponse": {
"answers": [
{
"id": @QID1,
"value": "Good"
},
{
"id": @QID2,
"value": ["Multi","Line"]
},
{
"id": @QID3,
"value": "Yellow"
}
]
}
}
49 changes: 49 additions & 0 deletions scripts/api/data/guestbook-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "my test guestbook",
"enabled": true,
"emailRequired": true,
"nameRequired": true,
"institutionRequired": false,
"positionRequired": false,
"customQuestions": [
{
"question": "how's your day",
"required": true,
"displayOrder": 0,
"type": "text",
"hidden": false
},
{
"question": "Describe yourself",
"required": false,
"displayOrder": 1,
"type": "textarea",
"hidden": false
},
{
"question": "What color car do you drive",
"required": true,
"displayOrder": 2,
"type": "options",
"hidden": false,
"optionValues": [
{
"value": "Red",
"displayOrder": 0
},
{
"value": "White",
"displayOrder": 1
},
{
"value": "Yellow",
"displayOrder": 2
},
{
"value": "Purple",
"displayOrder": 3
}
]
}
]
}
13 changes: 11 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/CustomQuestion.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package edu.harvard.iq.dataverse;
import java.io.Serializable;
import java.util.List;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;

import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;

/**
*
* @author skraffmiller
Expand Down Expand Up @@ -92,6 +95,12 @@ public void setQuestionString(String questionString) {
public List<CustomQuestionValue> getCustomQuestionValues() {
return customQuestionValues;
}

public List<String> getCustomQuestionOptions() {
return customQuestionValues.stream()
.map(CustomQuestionValue::getValueString)
.collect(Collectors.toList());
}

public String getCustomQuestionValueString(){
String retString = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
*/
package edu.harvard.iq.dataverse;

import java.io.Serializable;
import java.util.List;
import jakarta.faces.model.SelectItem;
import jakarta.persistence.*;

import java.io.Serializable;
import java.util.List;

/**
*
* @author skraffmiller
Expand Down
27 changes: 10 additions & 17 deletions src/main/java/edu/harvard/iq/dataverse/Guestbook.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,28 @@
package edu.harvard.iq.dataverse;

import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.DateUtil;
import jakarta.persistence.*;
import org.hibernate.validator.constraints.NotBlank;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import java.util.List;
import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.persistence.Transient;

import edu.harvard.iq.dataverse.util.DateUtil;
import org.hibernate.validator.constraints.NotBlank;

/**
*
* @author skraffmiller
*/
@Entity
@NamedQueries(
@NamedQuery(name = "Guestbook.findByDataverse",
query = "SELECT gb FROM Guestbook gb WHERE gb.dataverse=:dataverse")
)

public class Guestbook implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.externaltools.ExternalTool;
import edu.harvard.iq.dataverse.util.StringUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute;
Expand All @@ -30,9 +18,15 @@
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import jakarta.persistence.StoredProcedureQuery;
import jakarta.persistence.TypedQuery;
import org.apache.commons.text.StringEscapeUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.logging.Logger;
/**
*
* @author skraffmiller
Expand Down Expand Up @@ -815,7 +809,7 @@ public GuestbookResponse initAPIGuestbookResponse(Dataset dataset, DataFile data
}
guestbookResponse.setDataset(dataset);
guestbookResponse.setResponseTime(new Date());
guestbookResponse.setSessionId(session.toString());
guestbookResponse.setSessionId(session != null ? session.toString() : "");
guestbookResponse.setEventType(GuestbookResponse.DOWNLOAD);
setUserDefaultResponses(guestbookResponse, session, user);
return guestbookResponse;
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/GuestbookServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;

import java.util.List;

/**
*
* @author skraffmiller
Expand All @@ -21,8 +23,17 @@ public class GuestbookServiceBean implements java.io.Serializable {

@PersistenceContext(unitName = "VDCNet-ejbPU")
private EntityManager em;



public List<Guestbook> findGuestbooksForGivenDataverse(Dataverse dataverse) {
if (dataverse != null) {
Query query = em.createNamedQuery("Guestbook.findByDataverse");
query.setParameter("dataverse", dataverse);
return query.getResultList();
} else {
return List.of();
}
}

public Long findCountUsages(Long guestbookId, Long dataverseId) {
String queryString = "";
if (guestbookId != null && dataverseId != null) {
Expand Down
Loading