Skip to content

Commit ad8d686

Browse files
authored
Merge pull request #11819 from IQSS/11714-prevent-exclude-email-from-export-for-permitted-user-get-dataset-version
Prevent ExcludeEmailFromExport to hide the user email when an authorized user is obtaining metadata for editing a dataset
2 parents ee55052 + 0e8bfc4 commit ad8d686

7 files changed

Lines changed: 119 additions & 52 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
New query parameter (ignoreSettingExcludeEmailFromExport) for API /api/datasets/:persistentId/versions/{versionId}
2+
3+
SPA requires the ability to have the contact emails included in the response for this API call
4+
This query parameter prevents the contact email from being excluded when the setting (ExcludeEmailFromExport) is set to true and the user has EditDataset permissions.
5+
6+
See:
7+
- [#11714](https://github.com/IQSS/dataverse/issues/11714)

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,20 +1692,22 @@ Get JSON Representation of a Dataset
16921692

16931693
.. note:: Datasets can be accessed using persistent identifiers. This is done by passing the constant ``:persistentId`` where the numeric id of the dataset is expected, and then passing the actual persistent id as a query parameter with the name ``persistentId``.
16941694

1695+
If a user with EditDataset permissions wants to ignore the setting ``ExcludeEmailFromExport`` in order to see the contact email, they must include the ``ignoreSettingExcludeEmailFromExport`` query parameter (Required by SPA).
1696+
16951697
Example: Getting the dataset whose DOI is *10.5072/FK2/J8SJZB*:
16961698

16971699
.. code-block:: bash
16981700
16991701
export SERVER_URL=https://demo.dataverse.org
17001702
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/J8SJZB
17011703
1702-
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/datasets/:persistentId/?persistentId=$PERSISTENT_IDENTIFIER"
1704+
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/datasets/:persistentId/?persistentId=$PERSISTENT_IDENTIFIER&ignoreSettingExcludeEmailFromExport"
17031705
17041706
The fully expanded example above (without environment variables) looks like this:
17051707

17061708
.. code-block:: bash
17071709
1708-
curl -H "X-Dataverse-key:$API_TOKEN" "https://demo.dataverse.org/api/datasets/:persistentId/?persistentId=doi:10.5072/FK2/J8SJZB"
1710+
curl -H "X-Dataverse-key:$API_TOKEN" "https://demo.dataverse.org/api/datasets/:persistentId/?persistentId=doi:10.5072/FK2/J8SJZB&ignoreSettingExcludeEmailFromExport"
17091711
17101712
Getting its draft version:
17111713

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -475,12 +475,16 @@ public Response getVersion(@Context ContainerRequestContext crc,
475475
@QueryParam("excludeMetadataBlocks") Boolean excludeMetadataBlocks,
476476
@QueryParam("includeDeaccessioned") boolean includeDeaccessioned,
477477
@QueryParam("returnOwners") boolean returnOwners,
478+
@QueryParam("ignoreSettingExcludeEmailFromExport") Boolean ignoreSettingToExcludeEmailFromExport,
478479
@Context UriInfo uriInfo,
479480
@Context HttpHeaders headers) {
480481
return response( req -> {
482+
boolean includeMetadataBlocks = excludeMetadataBlocks == null ? true : !excludeMetadataBlocks;
483+
boolean includeFiles = excludeFiles == null ? true : !excludeFiles;
484+
boolean ignoreSettingExcludeEmailFromExport = ignoreSettingToExcludeEmailFromExport != null ? ignoreSettingToExcludeEmailFromExport : false;
481485

482486
//If excludeFiles is null the default is to provide the files and because of this we need to check permissions.
483-
boolean checkPerms = excludeFiles == null ? true : !excludeFiles;
487+
boolean checkPerms = includeFiles;
484488

485489
Dataset dataset = findDatasetOrDie(datasetId);
486490
DatasetVersion requestedDatasetVersion = getDatasetVersionOrDie(req,
@@ -494,16 +498,19 @@ public Response getVersion(@Context ContainerRequestContext crc,
494498
if (requestedDatasetVersion == null || requestedDatasetVersion.getId() == null) {
495499
return notFound("Dataset version not found");
496500
}
497-
498-
if (excludeFiles == null ? true : !excludeFiles) {
501+
if (includeFiles) {
499502
requestedDatasetVersion = datasetversionService.findDeep(requestedDatasetVersion.getId());
500503
}
501-
Boolean includeMetadataBlocks = excludeMetadataBlocks == null ? true : !excludeMetadataBlocks;
502504

503-
JsonObjectBuilder jsonBuilder = json(requestedDatasetVersion,
504-
null,
505-
excludeFiles == null ? true : !excludeFiles,
506-
returnOwners, includeMetadataBlocks);
505+
// Check to see if the caller wants to ignore the ExcludeEmailFromExport setting in the metadata block and that they have permission to do so
506+
// Let the JsonPrinter know to ignore the ExcludeEmailFromExport setting so the emails will show for this API call by permitted user
507+
if (ignoreSettingExcludeEmailFromExport && (!includeMetadataBlocks || !permissionService.userOn(getRequestUser(crc), dataset).has(Permission.EditDataset))) {
508+
// either not showing metadata block or user isn't allowed to override the setting
509+
ignoreSettingExcludeEmailFromExport = false;
510+
}
511+
512+
JsonObjectBuilder jsonBuilder = json(requestedDatasetVersion, null, includeFiles,
513+
returnOwners, includeMetadataBlocks, ignoreSettingExcludeEmailFromExport);
507514
return ok(jsonBuilder);
508515

509516
}, getRequestUser(crc));

src/main/java/edu/harvard/iq/dataverse/util/DatasetFieldWalker.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
66
import edu.harvard.iq.dataverse.DatasetFieldType;
77
import edu.harvard.iq.dataverse.DatasetFieldValue;
8-
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
98
import java.util.ArrayList;
109
import java.util.Collections;
1110
import java.util.Comparator;
1211
import java.util.List;
1312
import java.util.Map;
1413
import java.util.logging.Logger;
1514

16-
import jakarta.json.Json;
1715
import jakarta.json.JsonObject;
1816

1917
/**
@@ -46,21 +44,20 @@ public interface Listener {
4644
*/
4745
public static void walk( DatasetField dsf, Listener l, Map<Long, JsonObject> cvocMap ) {
4846
DatasetFieldWalker joe = new DatasetFieldWalker(l, cvocMap);
49-
SettingsServiceBean nullServiceBean = null;
50-
joe.walk(dsf, nullServiceBean);
47+
joe.walk(dsf, Collections.emptyList());
5148
}
5249

5350
/**
5451
* Convenience method to walk over a list of fields. Traversal
5552
* is done in display order.
5653
* @param fields the fields to go over. Does not have to be sorted.
57-
* @param exclude the fields to skip
54+
* @param excludedFieldTypeList the fields to skip
5855
* @param l the listener to execute on each field values and structure.
5956
*/
60-
public static void walk(List<DatasetField> fields, SettingsServiceBean settingsService, Map<Long, JsonObject> cvocMap, Listener l) {
57+
public static void walk(List<DatasetField> fields, List<DatasetFieldType.FieldType> excludedFieldTypeList, Map<Long, JsonObject> cvocMap, Listener l) {
6158
DatasetFieldWalker joe = new DatasetFieldWalker(l, cvocMap);
6259
for ( DatasetField dsf : sort( fields, DatasetField.DisplayOrder) ) {
63-
joe.walk(dsf, settingsService);
60+
joe.walk(dsf, excludedFieldTypeList);
6461
}
6562
}
6663

@@ -77,7 +74,7 @@ public DatasetFieldWalker(){
7774
this( null, null);
7875
}
7976

80-
public void walk(DatasetField fld, SettingsServiceBean settingsService) {
77+
public void walk(DatasetField fld, List<DatasetFieldType.FieldType> excludedFieldTypeList) {
8178
l.startField(fld);
8279
DatasetFieldType datasetFieldType = fld.getDatasetFieldType();
8380

@@ -89,7 +86,7 @@ public void walk(DatasetField fld, SettingsServiceBean settingsService) {
8986

9087
} else if ( datasetFieldType.isPrimitive() ) {
9188
for ( DatasetFieldValue pv : sort(fld.getDatasetFieldValues(), DatasetFieldValue.DisplayOrder) ) {
92-
if (settingsService != null && settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false) && DatasetFieldType.FieldType.EMAIL.equals(pv.getDatasetField().getDatasetFieldType().getFieldType())) {
89+
if (excludedFieldTypeList.contains(pv.getDatasetField().getDatasetFieldType().getFieldType())) {
9390
continue;
9491
}
9592
l.primitiveValue(pv);
@@ -99,15 +96,15 @@ public void walk(DatasetField fld, SettingsServiceBean settingsService) {
9996
for ( DatasetFieldCompoundValue dsfcv : sort( fld.getDatasetFieldCompoundValues(), DatasetFieldCompoundValue.DisplayOrder) ) {
10097
l.startCompoundValue(dsfcv);
10198
for ( DatasetField dsf : sort(dsfcv.getChildDatasetFields(), DatasetField.DisplayOrder ) ) {
102-
walk(dsf, settingsService);
99+
walk(dsf, excludedFieldTypeList);
103100
}
104101
l.endCompoundValue(dsfcv);
105102
}
106103
}
107104
l.addExpandedValuesArray(fld);
108105
if(datasetFieldType.isPrimitive() && cvocMap.containsKey(datasetFieldType.getId())) {
109106
for ( DatasetFieldValue evv : sort(fld.getDatasetFieldValues(), DatasetFieldValue.DisplayOrder) ) {
110-
if (settingsService != null && settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false) && DatasetFieldType.FieldType.EMAIL.equals(evv.getDatasetField().getDatasetFieldType().getFieldType())) {
107+
if (excludedFieldTypeList.contains(evv.getDatasetField().getDatasetFieldType().getFieldType())) {
111108
continue;
112109
}
113110
l.externalVocabularyValue(evv, cvocMap.get(datasetFieldType.getId()));

src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -513,17 +513,17 @@ public static JsonObjectBuilder json(FileDetailsHolder ds) {
513513
}
514514

515515
public static JsonObjectBuilder json(DatasetVersion dsv, boolean includeFiles) {
516-
return json(dsv, null, includeFiles, false, true);
516+
return json(dsv, null, includeFiles, false, true, false);
517517
}
518518
public static JsonObjectBuilder json(DatasetVersion dsv, boolean includeFiles, boolean includeMetadataBlocks) {
519-
return json(dsv, null, includeFiles, false, includeMetadataBlocks);
519+
return json(dsv, null, includeFiles, false, includeMetadataBlocks, false);
520520
}
521521
public static JsonObjectBuilder json(DatasetVersion dsv, List<String> anonymizedFieldTypeNamesList,
522522
boolean includeFiles, boolean returnOwners) {
523-
return json( dsv, anonymizedFieldTypeNamesList, includeFiles, returnOwners,true);
523+
return json(dsv, anonymizedFieldTypeNamesList, includeFiles, returnOwners, true, false);
524524
}
525525
public static JsonObjectBuilder json(DatasetVersion dsv, List<String> anonymizedFieldTypeNamesList,
526-
boolean includeFiles, boolean returnOwners, boolean includeMetadataBlocks) {
526+
boolean includeFiles, boolean returnOwners, boolean includeMetadataBlocks, boolean ignoreSettingExcludeEmailFromExport) {
527527
Dataset dataset = dsv.getDataset();
528528
JsonObjectBuilder bld = jsonObjectBuilder()
529529
.add("id", dsv.getId()).add("datasetId", dataset.getId())
@@ -572,10 +572,8 @@ public static JsonObjectBuilder json(DatasetVersion dsv, List<String> anonymized
572572
.add("studyCompletion", dsv.getTermsOfUseAndAccess().getStudyCompletion())
573573
.add("fileAccessRequest", dsv.getTermsOfUseAndAccess().isFileAccessRequest());
574574
if(includeMetadataBlocks) {
575-
bld.add("metadataBlocks", (anonymizedFieldTypeNamesList != null) ?
576-
jsonByBlocks(dsv.getDatasetFields(), anonymizedFieldTypeNamesList)
577-
: jsonByBlocks(dsv.getDatasetFields())
578-
);
575+
bld.add("metadataBlocks",
576+
jsonByBlocks(dsv.getDatasetFields(), anonymizedFieldTypeNamesList, ignoreSettingExcludeEmailFromExport));
579577
}
580578
if(returnOwners){
581579
bld.add("isPartOf", getOwnersFromDvObject(dataset));
@@ -659,15 +657,15 @@ public static JsonObjectBuilder json(DatasetDistributor dist) {
659657
}
660658

661659
public static JsonObjectBuilder jsonByBlocks(List<DatasetField> fields) {
662-
return jsonByBlocks(fields, null);
660+
return jsonByBlocks(fields, null, false);
663661
}
664662

665-
public static JsonObjectBuilder jsonByBlocks(List<DatasetField> fields, List<String> anonymizedFieldTypeNamesList) {
663+
public static JsonObjectBuilder jsonByBlocks(List<DatasetField> fields, List<String> anonymizedFieldTypeNamesList, boolean ignoreSettingExcludeEmailFromExport) {
666664
JsonObjectBuilder blocksBld = jsonObjectBuilder();
667665

668666
for (Map.Entry<MetadataBlock, List<DatasetField>> blockAndFields : DatasetField.groupByBlock(fields).entrySet()) {
669667
MetadataBlock block = blockAndFields.getKey();
670-
blocksBld.add(block.getName(), JsonPrinter.json(block, blockAndFields.getValue(), anonymizedFieldTypeNamesList));
668+
blocksBld.add(block.getName(), JsonPrinter.json(block, blockAndFields.getValue(), anonymizedFieldTypeNamesList, ignoreSettingExcludeEmailFromExport));
671669
}
672670
return blocksBld;
673671
}
@@ -685,14 +683,23 @@ public static JsonObjectBuilder json(MetadataBlock block, List<DatasetField> fie
685683
}
686684

687685
public static JsonObjectBuilder json(MetadataBlock block, List<DatasetField> fields, List<String> anonymizedFieldTypeNamesList) {
686+
return json(block, fields, anonymizedFieldTypeNamesList, false);
687+
}
688+
689+
public static JsonObjectBuilder json(MetadataBlock block, List<DatasetField> fields, List<String> anonymizedFieldTypeNamesList, boolean ignoreSettingExcludeEmailFromExport) {
688690
JsonObjectBuilder blockBld = jsonObjectBuilder();
689691

690692
blockBld.add("displayName", block.getDisplayName());
691693
blockBld.add("name", block.getName());
692694

693695
final JsonArrayBuilder fieldsArray = Json.createArrayBuilder();
694696
Map<Long, JsonObject> cvocMap = (datasetFieldService==null) ? new HashMap<Long, JsonObject>() :datasetFieldService.getCVocConf(true);
695-
DatasetFieldWalker.walk(fields, settingsService, cvocMap, new DatasetFieldsToJson(fieldsArray, anonymizedFieldTypeNamesList));
697+
List<DatasetFieldType.FieldType> excludedFieldTypeList = new ArrayList<>();
698+
// Exclude the Email field or override the exclusion of the Email field type based on the settings ExcludeEmailFromExport and ignoreSettingExcludeEmailFromExport
699+
if ((settingsService != null) && settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false) && !ignoreSettingExcludeEmailFromExport) {
700+
excludedFieldTypeList.add(DatasetFieldType.FieldType.EMAIL);
701+
}
702+
DatasetFieldWalker.walk(fields, excludedFieldTypeList, cvocMap, new DatasetFieldsToJson(fieldsArray, anonymizedFieldTypeNamesList));
696703

697704
blockBld.add("fields", fieldsArray);
698705
return blockBld;

src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@
3131
import org.apache.commons.lang3.StringUtils;
3232
import org.apache.commons.lang3.exception.ExceptionUtils;
3333
import org.hamcrest.CoreMatchers;
34-
import org.junit.jupiter.api.AfterAll;
35-
import org.junit.jupiter.api.BeforeAll;
36-
import org.junit.jupiter.api.Disabled;
37-
import org.junit.jupiter.api.Test;
34+
import org.junit.jupiter.api.*;
3835
import org.skyscreamer.jsonassert.JSONAssert;
3936

4037
import javax.xml.stream.XMLInputFactory;
@@ -79,10 +76,6 @@ public static void setUpClass() {
7976
removeIdentifierGenerationStyle.then().assertThat()
8077
.statusCode(200);
8178

82-
Response removeExcludeEmail = UtilIT.deleteSetting(SettingsServiceBean.Key.ExcludeEmailFromExport);
83-
removeExcludeEmail.then().assertThat()
84-
.statusCode(200);
85-
8679
Response removeAnonymizedFieldTypeNames = UtilIT.deleteSetting(SettingsServiceBean.Key.AnonymizedFieldTypeNames);
8780
removeAnonymizedFieldTypeNames.then().assertThat()
8881
.statusCode(200);
@@ -101,6 +94,10 @@ public static void setUpClass() {
10194
*/
10295
}
10396

97+
@AfterEach
98+
public void afterEach() {
99+
UtilIT.deleteSetting(SettingsServiceBean.Key.ExcludeEmailFromExport);
100+
}
104101

105102
@AfterAll
106103
public static void afterClass() {
@@ -109,10 +106,6 @@ public static void afterClass() {
109106
removeIdentifierGenerationStyle.then().assertThat()
110107
.statusCode(200);
111108

112-
Response removeExcludeEmail = UtilIT.deleteSetting(SettingsServiceBean.Key.ExcludeEmailFromExport);
113-
removeExcludeEmail.then().assertThat()
114-
.statusCode(200);
115-
116109
Response removeAnonymizedFieldTypeNames = UtilIT.deleteSetting(SettingsServiceBean.Key.AnonymizedFieldTypeNames);
117110
removeAnonymizedFieldTypeNames.then().assertThat()
118111
.statusCode(200);
@@ -1757,11 +1750,6 @@ public void testExcludeEmail() {
17571750
Response deleteUserResponse = UtilIT.deleteUser(username);
17581751
deleteUserResponse.prettyPrint();
17591752
assertEquals(200, deleteUserResponse.getStatusCode());
1760-
1761-
Response removeExcludeEmail = UtilIT.deleteSetting(SettingsServiceBean.Key.ExcludeEmailFromExport);
1762-
removeExcludeEmail.then().assertThat()
1763-
.statusCode(200);
1764-
17651753
}
17661754

17671755
@Disabled
@@ -7375,6 +7363,56 @@ public void testUpdateLicense() {
73757363
.statusCode(UNAUTHORIZED.getStatusCode());
73767364
}
73777365

7366+
@Test
7367+
public void testExcludeEmailOverride() {
7368+
// Create super user
7369+
String apiToken = getSuperuserToken();
7370+
// Create user with no permission
7371+
String apiTokenNoPerms = UtilIT.createRandomUserGetToken();
7372+
// Create Collection
7373+
String collectionAlias = UtilIT.createRandomCollectionGetAlias(apiToken);
7374+
// Publish Collection
7375+
UtilIT.publishDataverseViaNativeApi(collectionAlias, apiToken).prettyPrint();
7376+
// Create Dataset
7377+
Response createDataset = UtilIT.createRandomDatasetViaNativeApi(collectionAlias, apiToken);
7378+
createDataset.then().assertThat()
7379+
.statusCode(CREATED.getStatusCode());
7380+
Integer datasetId = UtilIT.getDatasetIdFromResponse(createDataset);
7381+
String datasetPid = JsonPath.from(createDataset.asString()).getString("data.persistentId");
7382+
// Publish Dataset
7383+
UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).prettyPrint();
7384+
7385+
// Setting is not set - datasetContactEmail will NOT be excluded
7386+
Response response = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_LATEST_PUBLISHED, apiToken);
7387+
response.then().assertThat().statusCode(OK.getStatusCode());
7388+
String json = response.prettyPrint();
7389+
assertTrue(json.contains("datasetContactName"));
7390+
assertTrue(json.contains("datasetContactEmail"));
7391+
7392+
UtilIT.setSetting(SettingsServiceBean.Key.ExcludeEmailFromExport, "true");
7393+
7394+
// User does not ignore the setting - datasetContactEmail will be excluded
7395+
response = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_LATEST_PUBLISHED, apiToken);
7396+
response.then().assertThat().statusCode(OK.getStatusCode());
7397+
json = response.prettyPrint();
7398+
assertTrue(json.contains("datasetContactName"));
7399+
assertTrue(!json.contains("datasetContactEmail"));
7400+
7401+
// User has permission to ignore the setting allowing the datasetContactEmail to be included in the response
7402+
response = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_LATEST_PUBLISHED, apiToken, true, false, false, true);
7403+
response.then().assertThat().statusCode(OK.getStatusCode());
7404+
json = response.prettyPrint();
7405+
assertTrue(json.contains("datasetContactName"));
7406+
assertTrue(json.contains("datasetContactEmail"));
7407+
7408+
// User has no permission to override the setting - datasetContactEmail will be excluded
7409+
response = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_LATEST_PUBLISHED, apiTokenNoPerms, true, false, false, true);
7410+
response.then().assertThat().statusCode(OK.getStatusCode());
7411+
json = response.prettyPrint();
7412+
assertTrue(json.contains("datasetContactName"));
7413+
assertTrue(!json.contains("datasetContactEmail"));
7414+
}
7415+
73787416
private String getSuperuserToken() {
73797417
Response createResponse = UtilIT.createRandomUser();
73807418
String adminApiToken = UtilIT.getApiTokenFromResponse(createResponse);

0 commit comments

Comments
 (0)