Skip to content

Commit

Permalink
Merge pull request #9599 from GlobalDataverseCommunityConsortium/GDCC…
Browse files Browse the repository at this point in the history
…-GBAtRequest

ADA/Guestbook-at-request
  • Loading branch information
kcondon authored Sep 29, 2023
2 parents ed8e966 + 7881919 commit fa97a76
Show file tree
Hide file tree
Showing 54 changed files with 1,866 additions and 610 deletions.
2 changes: 2 additions & 0 deletions doc/release-notes/9599-guestbook-at-request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Dataverse can now be configured (via the dataverse.files.guestbook-at-request option) to display any configured guestbook to users when they request restricted file(s) or when they download files (the historic default).
The global default defined by this setting can be overridden at the collection level on the collection page and at the individual dataset level by a superuser using the API. The default - showing guestbooks when files are downloaded - remains as it was in prior Dataverse versions.
45 changes: 45 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2261,6 +2261,51 @@ See :ref:`:CustomDatasetSummaryFields` in the Installation Guide for how the lis
curl "$SERVER_URL/api/datasets/summaryFieldNames"
.. _guestbook-at-request-api:

Configure When a Dataset Guestbook Appears (If Enabled)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, users are asked to fill out a configured Guestbook when they down download files from a dataset. If enabled for a given Dataverse instance (see XYZ), users may instead be asked to fill out a Guestbook only when they request access to restricted files.
This is configured by a global default, collection-level settings, or directly at the dataset level via these API calls (superuser access is required to make changes).

To see the current choice for this dataset:

.. code-block:: bash
export SERVER_URL=https://demo.dataverse.org
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/YD5QDG
curl "$SERVER_URL/api/datasets/:persistentId/guestbookEntryAtRequest?persistentId=$PERSISTENT_IDENTIFIER"
The response will be true (guestbook displays when making a request), false (guestbook displays at download), or will indicate that the dataset inherits one of these settings.
To set the behavior for this dataset:

.. code-block:: bash
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/YD5QDG
curl -X PUT -H "X-Dataverse-key:$API_TOKEN" -H Content-type:application/json -d true "$SERVER_URL/api/datasets/:persistentId/guestbookEntryAtRequest?persistentId=$PERSISTENT_IDENTIFIER"
This example uses true to set the behavior to guestbook at request. Note that this call will return a 403/Forbidden response if guestbook at request functionality is not enabled for this Dataverse instance.
The API can also be used to reset the dataset to use the default/inherited value:
.. code-block:: bash
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/YD5QDG
curl -X DELETE -H "X-Dataverse-key:$API_TOKEN" -H Content-type:application/json "$SERVER_URL/api/datasets/:persistentId/guestbookEntryAtRequest?persistentId=$PERSISTENT_IDENTIFIER"
Files
-----
Expand Down
11 changes: 11 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2455,6 +2455,17 @@ This setting was added to keep S3 direct upload lightweight. When that feature i

See also :ref:`s3-direct-upload-features-disabled`.

.. _dataverse.files.guestbook-at-request:

dataverse.files.guestbook-at-request
++++++++++++++++++++++++++++++++++++

This setting enables functionality to allow guestbooks to be displayed when a user requests access to a restricted data file(s) or when a file is downloaded (the historic default). Providing a true/false value for this setting enables the functionality and provides a global default. The behavior can also be changed at the collection level via the user interface and by a superuser for a give dataset using the API.

See also :ref:`guestbook-at-request-api` in the API Guide, and .

Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_FILES_GUESTBOOK_AT_REQUEST``.

.. _feature-flags:

Feature Flags
Expand Down
2 changes: 2 additions & 0 deletions doc/sphinx-guides/source/user/dataverse-management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Creating a Dataverse collection is easy but first you must be a registered user
* **Category**: Select a category that best describes the type of Dataverse collection this will be. For example, if this is a Dataverse collection for an individual researcher's datasets, select *Researcher*. If this is a Dataverse collection for an institution, select *Organization or Institution*.
* **Email**: This is the email address that will be used as the contact for this particular Dataverse collection. You can have more than one contact email address for your Dataverse collection.
* **Description**: Provide a description of this Dataverse collection. This will display on the landing page of your Dataverse collection and in the search result list. The description field supports certain HTML tags, if you'd like to format your text (<a>, <b>, <blockquote>, <br>, <code>, <del>, <dd>, <dl>, <dt>, <em>, <hr>, <h1>-<h3>, <i>, <img>, <kbd>, <li>, <ol>, <p>, <pre>, <s>, <sup>, <sub>, <strong>, <strike>, <u>, <ul>).
* **Dataset Metadata Langauge**: (If enabled) Select which language should be used when entering dataset metadata, or leave that choice to dataset creators.
* **Guestbook Entry Option**: (If enabled) Select whether guestbooks are displayed when a user requests access to restricted file(s) or when they initiate a download.
#. **Choose the sets of Metadata Fields for datasets in this Dataverse collection**:
* By default the metadata elements will be from the host Dataverse collection that this new Dataverse collection is created in.
* The Dataverse Software offers metadata standards for multiple domains. To learn more about the metadata standards in the Dataverse Software please check out the :doc:`/user/appendix`.
Expand Down
87 changes: 45 additions & 42 deletions src/main/java/edu/harvard/iq/dataverse/DataFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import edu.harvard.iq.dataverse.util.ShapefileHandler;
import edu.harvard.iq.dataverse.util.StringUtil;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;
Expand All @@ -33,7 +32,7 @@
import jakarta.json.JsonArrayBuilder;
import jakarta.persistence.*;
import jakarta.validation.constraints.Pattern;
import org.hibernate.validator.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;

/**
*
Expand All @@ -53,9 +52,9 @@
})
@Entity
@Table(indexes = {@Index(columnList="ingeststatus")
, @Index(columnList="checksumvalue")
, @Index(columnList="contenttype")
, @Index(columnList="restricted")})
, @Index(columnList="checksumvalue")
, @Index(columnList="contenttype")
, @Index(columnList="restricted")})
public class DataFile extends DvObject implements Comparable {
private static final Logger logger = Logger.getLogger(DatasetPage.class.getCanonicalName());
private static final long serialVersionUID = 1L;
Expand All @@ -73,10 +72,6 @@ public class DataFile extends DvObject implements Comparable {
@Pattern(regexp = "^.*/.*$", message = "{contenttype.slash}")
private String contentType;

public void setFileAccessRequests(List<FileAccessRequest> fileAccessRequests) {
this.fileAccessRequests = fileAccessRequests;
}

// @Expose
// @SerializedName("storageIdentifier")
// @Column( nullable = false )
Expand Down Expand Up @@ -200,6 +195,28 @@ public String toString() {
@OneToMany(mappedBy="dataFile", cascade={CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST})
private List<GuestbookResponse> guestbookResponses;

@OneToMany(mappedBy="dataFile",fetch = FetchType.LAZY,cascade={CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
private List<FileAccessRequest> fileAccessRequests;

@ManyToMany
@JoinTable(name = "fileaccessrequests",
joinColumns = @JoinColumn(name = "datafile_id"),
inverseJoinColumns = @JoinColumn(name = "authenticated_user_id"))
private List<AuthenticatedUser> fileAccessRequesters;


public List<FileAccessRequest> getFileAccessRequests(){
return fileAccessRequests;
}

public List<FileAccessRequest> getFileAccessRequests(FileAccessRequest.RequestState state){
return fileAccessRequests.stream().filter(far -> far.getState() == state).collect(Collectors.toList());
}

public void setFileAccessRequests(List<FileAccessRequest> fARs){
this.fileAccessRequests = fARs;
}

public List<GuestbookResponse> getGuestbookResponses() {
return guestbookResponses;
}
Expand Down Expand Up @@ -750,50 +767,38 @@ public String getUnf() {
return null;
}

@OneToMany(mappedBy = "dataFile", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}, orphanRemoval = true)
private List<FileAccessRequest> fileAccessRequests;
public List<AuthenticatedUser> getFileAccessRequesters() {
return fileAccessRequesters;
}

public List<FileAccessRequest> getFileAccessRequests() {
return fileAccessRequests;
public void setFileAccessRequesters(List<AuthenticatedUser> fileAccessRequesters) {
this.fileAccessRequesters = fileAccessRequesters;
}

public void addFileAccessRequester(AuthenticatedUser authenticatedUser) {

public void addFileAccessRequest(FileAccessRequest request) {
if (this.fileAccessRequests == null) {
this.fileAccessRequests = new ArrayList<>();
}

Set<AuthenticatedUser> existingUsers = this.fileAccessRequests.stream()
.map(FileAccessRequest::getAuthenticatedUser)
.collect(Collectors.toSet());
this.fileAccessRequests.add(request);
}

if (existingUsers.contains(authenticatedUser)) {
return;
public FileAccessRequest getAccessRequestForAssignee(RoleAssignee roleAssignee) {
if (this.fileAccessRequests == null) {
return null;
}

FileAccessRequest request = new FileAccessRequest();
request.setCreationTime(new Date());
request.setDataFile(this);
request.setAuthenticatedUser(authenticatedUser);

FileAccessRequest.FileAccessRequestKey key = new FileAccessRequest.FileAccessRequestKey();
key.setAuthenticatedUser(authenticatedUser.getId());
key.setDataFile(this.getId());

request.setId(key);

this.fileAccessRequests.add(request);
return this.fileAccessRequests.stream()
.filter(fileAccessRequest -> fileAccessRequest.getRequester().equals(roleAssignee) && fileAccessRequest.isStateCreated()).findFirst()
.orElse(null);
}

public boolean removeFileAccessRequester(RoleAssignee roleAssignee) {
public boolean removeFileAccessRequest(FileAccessRequest request) {
if (this.fileAccessRequests == null) {
return false;
}

FileAccessRequest request = this.fileAccessRequests.stream()
.filter(fileAccessRequest -> fileAccessRequest.getAuthenticatedUser().equals(roleAssignee))
.findFirst()
.orElse(null);

if (request != null) {
this.fileAccessRequests.remove(request);
return true;
Expand All @@ -802,13 +807,13 @@ public boolean removeFileAccessRequester(RoleAssignee roleAssignee) {
return false;
}

public boolean containsFileAccessRequestFromUser(RoleAssignee roleAssignee) {
public boolean containsActiveFileAccessRequestFromUser(RoleAssignee roleAssignee) {
if (this.fileAccessRequests == null) {
return false;
}

Set<AuthenticatedUser> existingUsers = this.fileAccessRequests.stream()
.map(FileAccessRequest::getAuthenticatedUser)
Set<AuthenticatedUser> existingUsers = getFileAccessRequests(FileAccessRequest.RequestState.CREATED).stream()
.map(FileAccessRequest::getRequester)
.collect(Collectors.toSet());

return existingUsers.contains(roleAssignee);
Expand Down Expand Up @@ -975,8 +980,6 @@ public String toJSON(){

public JsonObject asGsonObject(boolean prettyPrint){

String overarchingKey = "data";

GsonBuilder builder;
if (prettyPrint){ // Add pretty printing
builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting();
Expand Down
Loading

0 comments on commit fa97a76

Please sign in to comment.