Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
26818a7
#11772 prelim check in
sekmiller Sep 25, 2025
66e7572
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Sep 25, 2025
9fff5df
#11772 fix parser
sekmiller Sep 25, 2025
8a51ebc
#11772 fix parser and tests
sekmiller Sep 26, 2025
7085021
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Sep 30, 2025
9933f16
#11772 add content test
sekmiller Sep 30, 2025
3649cd4
#11772 fix typo
sekmiller Sep 30, 2025
10c0727
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Oct 1, 2025
c0c1f86
#11772 public install update/test
sekmiller Oct 1, 2025
7d16522
#11772 more tests for public install
sekmiller Oct 2, 2025
5881ef4
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Oct 2, 2025
fb5f911
#11772 remove unused file
sekmiller Oct 2, 2025
dda50b8
#11772 fix json/tests
sekmiller Oct 2, 2025
7bb99f9
#11772 add cleanup to tests
sekmiller Oct 2, 2025
653b93c
#11772 update command test
sekmiller Oct 2, 2025
b5fa671
#11772 update command test
sekmiller Oct 2, 2025
f612afb
#11772 update test
sekmiller Oct 3, 2025
97ffb6f
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Oct 3, 2025
ca15366
#11772 fix unit test
sekmiller Oct 6, 2025
a3b89bf
#11772 add another test
sekmiller Oct 8, 2025
682f78e
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Oct 8, 2025
327242b
#11772 fix test clean up
sekmiller Oct 8, 2025
d0af69f
#11772 split access update from terms
sekmiller Oct 8, 2025
4f6e277
#11772 separate parser for just access
sekmiller Oct 8, 2025
c0a64cc
Update native-api.rst
sekmiller Oct 8, 2025
aaf42ae
#11772 update json parser to match doc
sekmiller Oct 8, 2025
1a92c4c
#11772 clean up unit test
sekmiller Oct 9, 2025
3d9376c
#11771 add release notes
sekmiller Oct 9, 2025
9cf870b
#11772 rename release notes
sekmiller Oct 9, 2025
9cec541
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Oct 9, 2025
5390790
Update native-api.rst
sekmiller Oct 9, 2025
1252f38
Update native-api.rst
sekmiller Oct 9, 2025
a5daff4
Merge branch 'develop' into 11772-api-terms-of-access
sekmiller Oct 21, 2025
6e1ab29
Merge branch 'develop' into 11772-api-terms-of-access
stevenwinship Oct 21, 2025
7eddb70
Merge branch '11772-api-terms-of-access' of https://github.com/IQSS/d…
sekmiller Oct 21, 2025
621fe0a
#11772 updates from review/clarify
sekmiller Oct 21, 2025
1e594d7
#11772 changes suggested in CR
sekmiller Oct 21, 2025
ec9033a
Update doc/release-notes/11772-update-dataset-terms-of-access-api.md
ofahimIQSS Oct 24, 2025
f648084
Merge branch 'develop' into 11772-api-terms-of-access
ofahimIQSS Oct 24, 2025
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
12 changes: 12 additions & 0 deletions doc/release-notes/11772-update-dataset-terms-of-access-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## New Endpoint: `/datasets/{id}/access`

A new endpoint has been implemented to manage dataset terms of access for restricted files.

### Functionality
- Updates the terms of access of a dataset by applying it to the draft version.
- If no draft exists, a new one is automatically created.

### Usage

**Custom Terms of Access** – Provide a JSON body with the `customTermsOfAccess` object.
- All fields are optional **except** if there are restricted files in which case `fileAccessRequest` must be set to true or `termsOfAccess` must be provided.
40 changes: 40 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,46 @@ For these deletes your JSON file must include an exact match of those dataset fi

.. _publish-dataset-api:

Update Dataset Terms of Access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Updates the terms of access for the restricted files of a dataset by applying it to the draft version, or by creating a draft if none exists.


To define custom terms of access, provide a JSON body with the following properties. All fields within ``customTermsOfAccess`` are optional, except if there are restricted files in your dataset then ``fileAccessRequest`` must be set to true or ``termsOfAccess`` must be provided:

.. code-block:: json

[
{
"customTermsOfAccess": {
"fileAccessRequest": true,
"termsOfAccess": "Your terms of access for restricted files",
"dataAccessPlace": "Your data access place",
"originalArchive": "Your original archive",
"availabilityStatus": "Your availability status",
"contactForAccess": "Your contact for access",
"sizeOfCollection": "Your size of collection",
"studyCompletion": "Your study completion"
}
}
]

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=3
export FILE_PATH=access.json

curl -H "X-Dataverse-key:$API_TOKEN" -X PUT "$SERVER_URL/api/datasets/$ID/access" -H "Content-type:application/json" --upload-file $FILE_PATH

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

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X PUT "https://demo.dataverse.org/api/datasets/3/access" -H "Content-type:application/json" --upload-file access.json

Publish a Dataset
~~~~~~~~~~~~~~~~~

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,42 @@ public Response editVersionMetadata(@Context ContainerRequestContext crc, String
return ex.getResponse();
}
}

@PUT
@AuthRequired
@Path("{id}/access")
public Response editVersionTermsOfAccess(@Context ContainerRequestContext crc, String jsonBody, @PathParam("id") String id,
@QueryParam("sourceLastUpdateTime") String sourceLastUpdateTime) {
try {

boolean publicInstall = settingsSvc.isTrueForKey(SettingsServiceBean.Key.PublicInstall, false);

Dataset dataset = findDatasetOrDie(id);

if (sourceLastUpdateTime != null) {
validateInternalTimestampIsNotOutdated(dataset, sourceLastUpdateTime);
}

JsonObject json = JsonUtil.getJsonObject(jsonBody);

TermsOfUseAndAccess toua = jsonParser().parseTermsOfAccess(json);

if (publicInstall && (toua.isFileAccessRequest() || !toua.getTermsOfAccess().isEmpty())){
return error(BAD_REQUEST, "Setting File Access Request or Terms of Access is not permitted on a public installation.");
}

DatasetVersion updatedVersion = execCommand(new UpdateDatasetTermsOfAccessCommand(dataset, toua, createDataverseRequest(getRequestUser(crc)))).getLatestVersion();

return ok(json(updatedVersion, true));

} catch (JsonParseException ex) {
logger.log(Level.SEVERE, "Semantic error parsing dataset terms update Json: " + ex.getMessage(), ex);
return error(Response.Status.BAD_REQUEST, BundleUtil.getStringFromBundle("datasets.api.editMetadata.error.parseUpdate", List.of(ex.getMessage())));
} catch (WrappedResponse ex) {
logger.log(Level.SEVERE, "Update terms of use error: " + ex.getMessage(), ex);
return ex.getResponse();
}
}

/**
* @deprecated This was shipped as a GET but should have been a POST, see https://github.com/IQSS/dataverse/issues/2431
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

package edu.harvard.iq.dataverse.engine.command.impl;

import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetVersion;
import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.engine.command.CommandContext;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;


/**
*
* @author stephenkraffmiller
*/
@RequiredPermissions(Permission.EditDataset)
public class UpdateDatasetTermsOfAccessCommand extends AbstractDatasetCommand<Dataset>{


private final Dataset dataset;
private final TermsOfUseAndAccess termsOfUseAndAccess;
private final UpdateDatasetVersionCommand updateDatasetVersionCommand;

public UpdateDatasetTermsOfAccessCommand(Dataset dataset, TermsOfUseAndAccess termsOfUseAndAccess, DataverseRequest request) {
this(dataset, termsOfUseAndAccess, request, null);
}

//Command included for testing purposes
public UpdateDatasetTermsOfAccessCommand( Dataset dataset, TermsOfUseAndAccess termsOfUseAndAccess, DataverseRequest aRequest, UpdateDatasetVersionCommand updateDatasetVersionCommand ) {
super(aRequest, dataset);
this.dataset = dataset;
this.termsOfUseAndAccess = termsOfUseAndAccess;
this.updateDatasetVersionCommand = updateDatasetVersionCommand;
}

@Override
public Dataset execute(CommandContext ctxt) throws CommandException {
DatasetVersion datasetVersion = dataset.getOrCreateEditVersion();

datasetVersion.setTermsOfUseAndAccess(merge(datasetVersion,termsOfUseAndAccess));

datasetVersion.setVersionState(DatasetVersion.VersionState.DRAFT);
return ctxt.engine().submit(updateDatasetVersionCommand == null ? new UpdateDatasetVersionCommand(this.dataset, getRequest()) : updateDatasetVersionCommand);
}

private TermsOfUseAndAccess merge(DatasetVersion editVersion, TermsOfUseAndAccess incoming) {
//only update the access parts
TermsOfUseAndAccess termsToUpdate = editVersion.getTermsOfUseAndAccess();
termsToUpdate.setFileAccessRequest(incoming.isFileAccessRequest());
termsToUpdate.setTermsOfAccess(incoming.getTermsOfAccess());
termsToUpdate.setDataAccessPlace(incoming.getDataAccessPlace());
termsToUpdate.setOriginalArchive(incoming.getOriginalArchive());
termsToUpdate.setAvailabilityStatus(incoming.getAvailabilityStatus());
termsToUpdate.setContactForAccess(incoming.getContactForAccess());
termsToUpdate.setSizeOfCollection(incoming.getSizeOfCollection());
termsToUpdate.setStudyCompletion(incoming.getStudyCompletion());
return termsToUpdate;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public Dataset execute(CommandContext ctxt) throws CommandException {
if ( ! (getUser() instanceof AuthenticatedUser) ) {
throw new IllegalCommandException("Only authenticated users can update datasets", this);
}

Dataset theDataset = getDataset();
ctxt.permissions().checkUpdateDatasetVersionLock(theDataset, getRequest(), this);
Dataset savedDataset = null;
Expand Down Expand Up @@ -136,6 +136,7 @@ public Dataset execute(CommandContext ctxt) throws CommandException {
}

getDataset().getOrCreateEditVersion(fmVarMet).setDatasetFields(getDataset().getOrCreateEditVersion(fmVarMet).initDatasetFields());

validateOrDie(getDataset().getOrCreateEditVersion(fmVarMet), isValidateLenient());

final DatasetVersion editVersion = getDataset().getOrCreateEditVersion(fmVarMet);
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,51 @@ public static <E extends Enum<E>> List<E> parseEnumsFromArray(JsonArray enumsArr
}
return enums;
}

public TermsOfUseAndAccess parseTermsOfUseAndAccess(JsonObject obj) throws JsonParseException {
JsonObject terms = obj.getJsonObject("termsOfUseAndAccess");
TermsOfUseAndAccess toaa = new TermsOfUseAndAccess();
toaa.setTermsOfUse(terms.getString("termsOfUse", null));
toaa.setConfidentialityDeclaration(terms.getString("confidentialityDeclaration", null));
toaa.setSpecialPermissions(terms.getString("specialPermissions", null));
toaa.setRestrictions(terms.getString("restrictions", null));
toaa.setCitationRequirements(terms.getString("citationRequirements", null));
toaa.setDepositorRequirements(terms.getString("depositorRequirements", null));
toaa.setConditions(terms.getString("conditions", null));
toaa.setDisclaimer(terms.getString("disclaimer", null));
return parseTermsOfAccess(obj, toaa);
}

public TermsOfUseAndAccess parseTermsOfAccess(JsonObject obj) throws JsonParseException {
return parseTermsOfAccess(obj, null);
}

public TermsOfUseAndAccess parseTermsOfAccess(JsonObject obj, TermsOfUseAndAccess touaIn) throws JsonParseException {
//This only gets values associated with the terms of access for restricted files when no TermsOfUseAndAccess object provided
// or added to an existing object when provided

JsonObject terms;
TermsOfUseAndAccess toaa;
if (touaIn == null) {
terms = obj.getJsonObject("customTermsOfAccess");
toaa = new TermsOfUseAndAccess();
} else {
terms = obj.getJsonObject("termsOfUseAndAccess");
toaa = touaIn;
}

toaa.setFileAccessRequest(terms.getBoolean("fileAccessRequest", false));
toaa.setTermsOfAccess(terms.getString("termsOfAccess", null));
toaa.setDataAccessPlace(terms.getString("dataAccessPlace", null));
toaa.setOriginalArchive(terms.getString("originalArchive", null));
toaa.setAvailabilityStatus(terms.getString("availabilityStatus", null));
toaa.setContactForAccess(terms.getString("contactForAccess", null));
toaa.setSizeOfCollection(terms.getString("sizeOfCollection", null));
toaa.setStudyCompletion(terms.getString("studyCompletion", null));
toaa.setConfidentialityDeclaration(terms.getString("confidentialityDeclaration", null));

return toaa;
}

public DatasetVersion parseDatasetVersion(JsonObject obj) throws JsonParseException {
return parseDatasetVersion(obj, new DatasetVersion());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ public static JsonObjectBuilder json(FileDetailsHolder ds) {
}

public static JsonObjectBuilder json(DatasetVersion dsv, boolean includeFiles) {
return json(dsv, null, includeFiles, false,true);
return json(dsv, null, includeFiles, false, true);
}
public static JsonObjectBuilder json(DatasetVersion dsv, boolean includeFiles, boolean includeMetadataBlocks) {
return json(dsv, null, includeFiles, false, includeMetadataBlocks);
Expand Down
Loading