Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v3.23.24 - 2026-10-09

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v3.24.8...v3.23.24)

## v3.23.22 - 2026-10-05

[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v3.23.21...v3.23.22)
Expand Down
7 changes: 0 additions & 7 deletions orcid-api-web/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,6 @@
</dependency>

<!-- Test dependencies -->
<!-- Togglz testing support -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-junit</artifactId>
<version>2.4.1.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>orcid-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ public Response viewActivities(String orcid) {
public Response viewWork(String orcid, Long putCode) {
Work w = workManagerReadOnly.getWork(orcid, putCode);
orcidSecurityManager.checkAndFilter(orcid, w, ScopePathType.ORCID_WORKS_READ_LIMITED);
contributorUtils.filterContributorPrivateData(w);
ActivityUtils.cleanEmptyFields(w);
ActivityUtils.setPathToActivity(w, orcid);
sourceUtils.setSourceName(w);
Expand Down Expand Up @@ -1090,6 +1091,7 @@ public Response viewBulkWorks(String orcid, String putCodes) {

WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes);
orcidSecurityManager.checkAndFilter(orcid, workBulk, ScopePathType.ORCID_WORKS_READ_LIMITED);
contributorUtils.filterContributorPrivateData(workBulk);
ActivityUtils.cleanEmptyFields(workBulk);
ActivityUtils.setPathToBulk(workBulk, orcid);
sourceUtils.setSourceName(workBulk);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ public Response viewWork(String orcid, Long putCode) {
checkProfileStatus(orcid, true);
Work w = workManagerReadOnly.getWork(orcid, putCode);
orcidSecurityManager.checkAndFilter(orcid, w, ScopePathType.ORCID_WORKS_READ_LIMITED);
contributorUtils.filterContributorPrivateData(w);
ActivityUtils.cleanEmptyFields(w);
ActivityUtils.setPathToActivity(w, orcid);
sourceUtils.setSourceName(w);
Expand Down Expand Up @@ -1114,6 +1115,7 @@ public Response viewBulkWorks(String orcid, String putCodes) {

WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes);
orcidSecurityManager.checkAndFilter(orcid, workBulk, ScopePathType.ORCID_WORKS_READ_LIMITED);
contributorUtils.filterContributorPrivateData(workBulk);
ActivityUtils.cleanEmptyFields(workBulk);
ActivityUtils.setPathToBulk(workBulk, orcid);
sourceUtils.setSourceName(workBulk);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,27 @@ public void testViewWorkReadPublic() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
Utils.assertIsPublicOrSource(work, "APP-5555555555555555");
}

// Work 11 was deposited by APP-5555555555555555; the email is withheld from every caller
@Test
public void testViewWorkOmitsContributorEmailForAnotherClientReadPublic() {
SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-6666666666666666", ScopePathType.READ_PUBLIC);
Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
assertEquals(1, work.getWorkContributors().getContributor().size());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}

@Test
public void testViewWorkOmitsContributorEmailForAnotherClientReadLimited() {
SecurityContextTestUtils.setUpSecurityContext(ORCID, "APP-6666666666666666", ScopePathType.READ_LIMITED);
Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
assertEquals(1, work.getWorkContributors().getContributor().size());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}

@Test
public void testViewWorkSummaryReadPublic() {
SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-5555555555555555", ScopePathType.READ_PUBLIC);
Expand Down Expand Up @@ -729,6 +747,9 @@ public void testViewBulkWorks() {
assertEquals("/0000-0000-0000-0003/work/11", ((Work) workBulk.getBulk().get(0)).getPath());
assertEquals("/0000-0000-0000-0003/work/12", ((Work) workBulk.getBulk().get(1)).getPath());
assertEquals("/0000-0000-0000-0003/work/13", ((Work) workBulk.getBulk().get(2)).getPath());
Work work11 = (Work) workBulk.getBulk().get(0);
assertEquals(1, work11.getWorkContributors().getContributor().size());
assertNull(work11.getWorkContributors().getContributor().get(0).getContributorEmail());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -1984,6 +1985,26 @@ public void testDeactivatedRecordDeleteAddress() {
fail();
}

/**
* The contributor email is write-only. The versioned beans run every work through the
* 2.0 <-> 2.1 converter, which copies every bean property, so check it stays out on both.
*/
@Test
public void testViewWorkOmitsContributorEmailOnV2_0AndV2_1() {
SecurityContextTestUtils.setUpSecurityContext("0000-0000-0000-0003", ScopePathType.READ_LIMITED);
assertNoContributorEmail((Work) serviceDelegator.viewWork("0000-0000-0000-0003", 11L).getEntity());
assertNoContributorEmail((Work) serviceDelegator_v2_1.viewWork("0000-0000-0000-0003", 11L).getEntity());
assertNoContributorEmail((Work) ((WorkBulk) serviceDelegator.viewBulkWorks("0000-0000-0000-0003", "11").getEntity()).getBulk().get(0));
assertNoContributorEmail((Work) ((WorkBulk) serviceDelegator_v2_1.viewBulkWorks("0000-0000-0000-0003", "11").getEntity()).getBulk().get(0));
}

private void assertNoContributorEmail(Work work) {
assertEquals(Long.valueOf(11), work.getPutCode());
assertEquals(1, work.getWorkContributors().getContributor().size());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}

private void assertSourceElement(SourceAware element, boolean isHttps) {
if(element.getSource() != null && element.getSource().getSourceOrcid() != null) {
assertProtocol(element.getSource().getSourceOrcid().getUri(), isHttps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,27 @@ public void testViewWorkReadPublic() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
Utils.assertIsPublicOrSource(work, "APP-5555555555555555");
}

// Work 11 was deposited by APP-5555555555555555; the email is withheld from every caller
@Test
public void testViewWorkOmitsContributorEmailForAnotherClientReadPublic() {
SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-6666666666666666", ScopePathType.READ_PUBLIC);
Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
assertEquals(1, work.getWorkContributors().getContributor().size());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}

@Test
public void testViewWorkOmitsContributorEmailForAnotherClientReadLimited() {
SecurityContextTestUtils.setUpSecurityContext(ORCID, "APP-6666666666666666", ScopePathType.READ_LIMITED);
Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
assertEquals(1, work.getWorkContributors().getContributor().size());
assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}

@Test
public void testViewWorkSummaryReadPublic() {
SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-5555555555555555", ScopePathType.READ_PUBLIC);
Expand Down Expand Up @@ -716,6 +734,9 @@ public void testViewBulkWorks() {
assertEquals("/0000-0000-0000-0003/work/11", ((Work) workBulk.getBulk().get(0)).getPath());
assertEquals("/0000-0000-0000-0003/work/12", ((Work) workBulk.getBulk().get(1)).getPath());
assertEquals("/0000-0000-0000-0003/work/13", ((Work) workBulk.getBulk().get(2)).getPath());
Work work11 = (Work) workBulk.getBulk().get(0);
assertEquals(1, work11.getWorkContributors().getContributor().size());
assertNull(work11.getWorkContributors().getContributor().get(0).getContributorEmail());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package org.orcid.core.adapter.mapstruct;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import org.orcid.core.manager.IdentityProviderManager;
import org.orcid.core.manager.impl.OrcidUrlManager;
import org.orcid.core.utils.JsonUtils;
import org.orcid.jaxb.model.notification.amended_v2.NotificationAmended;
import org.orcid.jaxb.model.notification.permission_v2.AuthorizationUrl;
import org.orcid.jaxb.model.notification.permission_v2.Item;
import org.orcid.jaxb.model.notification.permission_v2.Items;
import org.orcid.jaxb.model.notification.permission_v2.NotificationPermission;
import org.orcid.jaxb.model.v3.release.notification.amended.AmendedSection;
import org.orcid.model.notification.institutional_sign_in_v2.NotificationInstitutionalConnection;
Expand All @@ -16,11 +24,51 @@
import org.orcid.persistence.jpa.entities.NotificationInstitutionalConnectionEntity;
import org.orcid.persistence.jpa.entities.NotificationItemEntity;

@Mapper
@Mapper(componentModel = "spring", uses = { ExternalIdentifierTypeMapper.class, UrlMapperV2.class })
public interface NotificationMapperV2 {

NotificationMapperV2 INSTANCE = Mappers.getMapper(NotificationMapperV2.class);

@Mapping(target = "id", ignore = true)
@Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb")
@Mapping(source = "externalIdentifier.value", target = "externalIdValue")
@Mapping(source = "externalIdentifier.url", target = "externalIdUrl")
@Mapping(source = "externalIdentifier.relationship", target = "externalIdRelationship")
@Mapping(source = "additionalInfo", target = "additionalInfo")
NotificationItemEntity toNotificationItemEntity(Item item);

@Mapping(source = "id", target = "putCode")
@Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi")
@Mapping(source = "externalIdValue", target = "externalIdentifier.value")
@Mapping(source = "externalIdUrl", target = "externalIdentifier.url")
@Mapping(source = "externalIdRelationship", target = "externalIdentifier.relationship")
@Mapping(source = "additionalInfo", target = "additionalInfo")
Item toItem(NotificationItemEntity entity);

@AfterMapping
default void afterToItem(NotificationItemEntity entity, @MappingTarget Item item) {
if (StringUtils.isBlank(entity.getExternalIdType()) && StringUtils.isBlank(entity.getExternalIdValue())
&& StringUtils.isBlank(entity.getExternalIdUrl()) && StringUtils.isBlank(entity.getExternalIdRelationship())) {
item.setExternalIdentifier(null);
}
}

@SuppressWarnings("rawtypes")
default String mapAdditionalInfo(Map map) {
if (map == null || map.isEmpty()) {
return null;
}
return JsonUtils.convertToJsonString(map);
}

@SuppressWarnings("rawtypes")
default Map mapAdditionalInfo(String json) {
if (StringUtils.isBlank(json)) {
return null;
}
return JsonUtils.readObjectFromJsonString(json, HashMap.class);
}

default String buildAuthorizationUrlIfBlank(String existingUrl, String path, OrcidUrlManager orcidUrlManager) {
if (StringUtils.isBlank(existingUrl)) {
return orcidUrlManager.getBaseUrl() + path;
Expand All @@ -34,6 +82,9 @@ default void mapPermissionBtoA(NotificationAddItemsEntity entity, NotificationPe
authUrl.setPath(fullPath);
authUrl.setHost(baseHost);
}
if (notification.getItems() == null) {
notification.setItems(new Items());
}
}

default void mapInstitutionalBtoA(NotificationInstitutionalConnectionEntity entity, NotificationInstitutionalConnection notification, String fullPath,
Expand Down Expand Up @@ -66,7 +117,6 @@ default void mapAmendedAtoB(NotificationAmended model, NotificationAmendedEntity
if (model.getAmendedSection() == null) {
return;
}

switch (model.getAmendedSection()) {
case AFFILIATION:
entity.setAmendedSection(AmendedSection.AFFILIATION.name());
Expand Down Expand Up @@ -108,6 +158,9 @@ default void mapAmendedAtoB(NotificationAmended model, NotificationAmendedEntity
}

default void mapAmendedBtoA(NotificationAmendedEntity entity, NotificationAmended model) {
if (model.getItems() == null) {
model.setItems(new Items());
}
if (entity.getAmendedSection() == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,75 @@
package org.orcid.core.adapter.mapstruct;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import org.orcid.core.manager.IdentityProviderManager;
import org.orcid.core.utils.JsonUtils;
import org.orcid.jaxb.model.v3.release.notification.amended.NotificationAmended;
import org.orcid.jaxb.model.v3.release.notification.permission.AuthorizationUrl;
import org.orcid.jaxb.model.v3.release.notification.permission.Item;
import org.orcid.jaxb.model.v3.release.notification.permission.Items;
import org.orcid.jaxb.model.v3.release.notification.permission.NotificationPermission;
import org.orcid.model.v3.release.notification.institutional_sign_in.NotificationInstitutionalConnection;
import org.orcid.model.v3.release.notification.internal.NotificationFindMyStuff;
import org.orcid.persistence.jpa.entities.NotificationAddItemsEntity;
import org.orcid.persistence.jpa.entities.NotificationAmendedEntity;
import org.orcid.persistence.jpa.entities.NotificationFindMyStuffEntity;
import org.orcid.persistence.jpa.entities.NotificationInstitutionalConnectionEntity;
import org.orcid.persistence.jpa.entities.NotificationItemEntity;
import org.orcid.pojo.ajaxForm.PojoUtil;

@Mapper
@Mapper(componentModel = "spring", uses = { ExternalIdentifierTypeMapper.class, UrlMapperV3.class })
public interface NotificationMapperV3 {

NotificationMapperV3 INSTANCE = Mappers.getMapper(NotificationMapperV3.class);

@Mapping(target = "id", ignore = true)
@Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb")
@Mapping(source = "externalIdentifier.value", target = "externalIdValue")
@Mapping(source = "externalIdentifier.url", target = "externalIdUrl")
@Mapping(source = "externalIdentifier.relationship", target = "externalIdRelationship")
@Mapping(source = "additionalInfo", target = "additionalInfo")
NotificationItemEntity toNotificationItemEntity(Item item);

@Mapping(source = "id", target = "putCode")
@Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi")
@Mapping(source = "externalIdValue", target = "externalIdentifier.value")
@Mapping(source = "externalIdUrl", target = "externalIdentifier.url")
@Mapping(source = "externalIdRelationship", target = "externalIdentifier.relationship")
@Mapping(source = "additionalInfo", target = "additionalInfo")
Item toItem(NotificationItemEntity entity);

@AfterMapping
default void afterToItem(NotificationItemEntity entity, @MappingTarget Item item) {
if (StringUtils.isBlank(entity.getExternalIdType()) && StringUtils.isBlank(entity.getExternalIdValue())
&& StringUtils.isBlank(entity.getExternalIdUrl()) && StringUtils.isBlank(entity.getExternalIdRelationship())) {
item.setExternalIdentifier(null);
}
}

@SuppressWarnings("rawtypes")
default String mapAdditionalInfo(Map map) {
if (map == null || map.isEmpty()) {
return null;
}
return JsonUtils.convertToJsonString(map);
}

@SuppressWarnings("rawtypes")
default Map mapAdditionalInfo(String json) {
if (PojoUtil.isEmpty(json)) {
return null;
}
return JsonUtils.readObjectFromJsonString(json, HashMap.class);
}

default String buildAuthorizationUrlIfBlank(String existingUrl, String path, String baseUrl) {
if (StringUtils.isBlank(existingUrl)) {
return baseUrl + path;
Expand All @@ -32,6 +83,9 @@ default void mapPermissionBtoA(NotificationAddItemsEntity entity, NotificationPe
authUrl.setPath(fullPath);
authUrl.setHost(baseHost);
}
if (notification.getItems() == null) {
notification.setItems(new Items());
}
}

default void mapInstitutionalBtoA(NotificationInstitutionalConnectionEntity entity, NotificationInstitutionalConnection notification, String fullPath,
Expand All @@ -50,6 +104,12 @@ default void mapInstitutionalBtoA(NotificationInstitutionalConnectionEntity enti
}
}

default void mapAmendedBtoA(NotificationAmendedEntity entity, NotificationAmended notification) {
if (notification.getItems() == null) {
notification.setItems(new Items());
}
}

default void mapFindMyStuffAtoB(NotificationFindMyStuff notification, NotificationFindMyStuffEntity entity, String existingAuthorizationUrl,
String builtAuthorizationUrl) {
if (StringUtils.isBlank(existingAuthorizationUrl)) {
Expand Down
Loading
Loading