diff --git a/CHANGELOG.md b/CHANGELOG.md index f918a6bc35e..e3cff39b8d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/orcid-api-web/pom.xml b/orcid-api-web/pom.xml index 2379d667abe..d209cabb951 100644 --- a/orcid-api-web/pom.xml +++ b/orcid-api-web/pom.xml @@ -67,13 +67,6 @@ - - - org.togglz - togglz-junit - 2.4.1.Final - test - ${project.groupId} orcid-test diff --git a/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java b/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java index d2ba9aba218..154d4a8c344 100644 --- a/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java +++ b/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java @@ -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); @@ -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); diff --git a/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java b/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java index b7f727fd197..e8f0b9d89d4 100644 --- a/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java +++ b/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java @@ -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); @@ -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); diff --git a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java index ac9c4ad87db..fbe9d43fc23 100644 --- a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java +++ b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java @@ -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); @@ -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 diff --git a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java index f0e4a850319..b5119067e99 100644 --- a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java +++ b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java @@ -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; @@ -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); diff --git a/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java b/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java index 12819562718..7da0c8b2ecc 100644 --- a/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java +++ b/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java @@ -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); @@ -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 diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java index 4fa7f592d11..000e570c0ac 100644 --- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java +++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java @@ -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; @@ -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; @@ -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, @@ -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()); @@ -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; } diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java index dcacdd647d8..6951f11afe8 100644 --- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java +++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java @@ -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; @@ -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, @@ -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)) { diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java index 5a8bf94f267..a5bb5663381 100644 --- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java +++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java @@ -22,7 +22,9 @@ import org.orcid.core.adapter.JpaJaxbNotificationAdapter; import org.orcid.core.adapter.mapstruct.ExternalIdentifierTypeMapper; +import org.orcid.core.adapter.mapstruct.NotificationMapperV2; import org.orcid.core.adapter.mapstruct.SourceMapperV2; +import org.orcid.core.adapter.mapstruct.UrlMapperV2; import org.orcid.core.exception.OrcidValidationException; import org.orcid.core.manager.IdentityProviderManager; import org.orcid.core.manager.impl.OrcidUrlManager; @@ -34,6 +36,7 @@ import org.orcid.jaxb.model.notification.custom_v2.NotificationTip; 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.notification_v2.Notification; import org.orcid.model.notification.institutional_sign_in_v2.NotificationInstitutionalConnection; @@ -44,12 +47,15 @@ */ @Mapper( componentModel = "spring", - uses = {SourceMapperV2.class, ExternalIdentifierTypeMapper.class} + uses = {SourceMapperV2.class, NotificationMapperV2.class, ExternalIdentifierTypeMapper.class, UrlMapperV2.class} ) public abstract class JpaJaxbNotificationAdapterImpl implements JpaJaxbNotificationAdapter { private static final String LAST_RESORT_IDENTITY_PROVIDER_NAME = "identity provider"; + @Autowired + protected NotificationMapperV2 notificationMapperV2; + @Autowired protected OrcidUrlManager orcidUrlManager; @@ -158,11 +164,11 @@ protected void afterMapPermission(NotificationPermission n, @MappingTarget Notif @AfterMapping protected void afterMapPermissionEntity(NotificationAddItemsEntity entity, @MappingTarget NotificationPermission n) { - AuthorizationUrl authUrl = n.getAuthorizationUrl(); - if (authUrl != null && authUrl.getUri() != null) { - authUrl.setPath(extractFullPath(authUrl.getUri())); - authUrl.setHost(orcidUrlManager.getBaseHost()); + String fullPath = null; + if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) { + fullPath = extractFullPath(n.getAuthorizationUrl().getUri()); } + notificationMapperV2.mapPermissionBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost()); } // 6. Notification Institutional Connection @@ -188,71 +194,36 @@ protected void afterMapInstitutionalConnection(NotificationInstitutionalConnecti @AfterMapping protected void afterMapInstitutionalConnectionEntity(NotificationInstitutionalConnectionEntity entity, @MappingTarget NotificationInstitutionalConnection n) { - AuthorizationUrl authUrl = n.getAuthorizationUrl(); - if (authUrl != null && authUrl.getUri() != null) { - authUrl.setPath(extractFullPath(authUrl.getUri())); - authUrl.setHost(orcidUrlManager.getBaseHost()); - } - - String providerId = entity.getAuthenticationProviderId(); - if (StringUtils.isNotBlank(providerId)) { - String idpName = identityProviderManager.retrieveIdentitifyProviderName(providerId); - n.setIdpName(idpName); - } else { - n.setIdpName(LAST_RESORT_IDENTITY_PROVIDER_NAME); + String fullPath = null; + if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) { + fullPath = extractFullPath(n.getAuthorizationUrl().getUri()); } + notificationMapperV2.mapInstitutionalBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost(), identityProviderManager, + LAST_RESORT_IDENTITY_PROVIDER_NAME); } // 7. Notification Amended @Mapping(source = "putCode", target = "id") @Mapping(target = "dateCreated", ignore = true) @Mapping(source = "items.items", target = "notificationItems") - @Mapping(source = "amendedSection", target = "amendedSection") + @Mapping(target = "amendedSection", ignore = true) protected abstract NotificationAmendedEntity map(NotificationAmended n); @Mapping(source = "id", target = "putCode") @Mapping(source = "dateCreated", target = "createdDate") @Mapping(source = "notificationItems", target = "items.items") - @Mapping(source = "amendedSection", target = "amendedSection") + @Mapping(target = "amendedSection", ignore = true) @Mapping(source = ".", target = "source") protected abstract NotificationAmended map(NotificationAmendedEntity e); - // Custom AmendedSection Enum Converters - protected String mapAmendedSection(org.orcid.jaxb.model.notification.amended_v2.AmendedSection section) { - if (section == null) return org.orcid.jaxb.model.v3.release.notification.amended.AmendedSection.UNKNOWN.name(); - return section.name(); - } - - protected org.orcid.jaxb.model.notification.amended_v2.AmendedSection mapAmendedSection(String section) { - if (StringUtils.isBlank(section)) return org.orcid.jaxb.model.notification.amended_v2.AmendedSection.UNKNOWN; - try { - return org.orcid.jaxb.model.notification.amended_v2.AmendedSection.valueOf(section); - } catch (IllegalArgumentException e) { - return org.orcid.jaxb.model.notification.amended_v2.AmendedSection.AFFILIATION; - } - } - - // Notification Items Mapping - @Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb") - @Mapping(source = "externalIdentifier.value", target = "externalIdValue") - @Mapping(source = "additionalInfo", target = "additionalInfo") - protected abstract NotificationItemEntity mapItem(Item item); - - @Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi") - @Mapping(source = "externalIdValue", target = "externalIdentifier.value") - @Mapping(source = "additionalInfo", target = "additionalInfo") - protected abstract Item mapItem(NotificationItemEntity entity); - - @SuppressWarnings("rawtypes") - protected String mapAdditionalInfo(Map map) { - if (map == null || map.isEmpty()) return null; - return JsonUtils.convertToJsonString(map); + @AfterMapping + protected void afterMapAmended(NotificationAmended n, @MappingTarget NotificationAmendedEntity entity) { + notificationMapperV2.mapAmendedAtoB(n, entity); } - @SuppressWarnings("rawtypes") - protected Map mapAdditionalInfo(String json) { - if (StringUtils.isBlank(json)) return null; - return JsonUtils.readObjectFromJsonString(json, HashMap.class); + @AfterMapping + protected void afterMapAmendedEntity(NotificationAmendedEntity entity, @MappingTarget NotificationAmended n) { + notificationMapperV2.mapAmendedBtoA(entity, n); } // XMLGregorianCalendar <-> java.util.Date Converters diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java index 89c319150b9..a0b26be42b5 100644 --- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java +++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java @@ -20,10 +20,11 @@ import org.mapstruct.MappingTarget; import org.springframework.beans.factory.annotation.Autowired; +import org.orcid.core.adapter.v3.JpaJaxbNotificationAdapter; import org.orcid.core.adapter.mapstruct.ExternalIdentifierTypeMapper; +import org.orcid.core.adapter.mapstruct.NotificationMapperV3; import org.orcid.core.adapter.mapstruct.SourceMapperV3; import org.orcid.core.adapter.mapstruct.UrlMapperV3; -import org.orcid.core.adapter.v3.JpaJaxbNotificationAdapter; import org.orcid.core.exception.OrcidValidationException; import org.orcid.core.manager.IdentityProviderManager; import org.orcid.core.manager.impl.OrcidUrlManager; @@ -36,6 +37,7 @@ import org.orcid.jaxb.model.v3.release.notification.custom.NotificationTip; 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; @@ -53,12 +55,15 @@ @Mapper( componentModel = "spring", - uses = {SourceMapperV3.class, ExternalIdentifierTypeMapper.class, UrlMapperV3.class} + uses = {SourceMapperV3.class, NotificationMapperV3.class, ExternalIdentifierTypeMapper.class, UrlMapperV3.class} ) public abstract class JpaJaxbNotificationAdapterImpl implements JpaJaxbNotificationAdapter { private static final String LAST_RESORT_IDENTITY_PROVIDER_NAME = "identity provider"; + @Autowired + protected NotificationMapperV3 notificationMapperV3; + @Autowired protected OrcidUrlManager orcidUrlManager; @@ -169,11 +174,11 @@ protected void afterMapPermission(NotificationPermission n, @MappingTarget Notif @AfterMapping protected void afterMapPermissionEntity(NotificationAddItemsEntity entity, @MappingTarget NotificationPermission n) { - AuthorizationUrl authUrl = n.getAuthorizationUrl(); - if (authUrl != null && authUrl.getUri() != null) { - authUrl.setPath(extractFullPath(authUrl.getUri())); - authUrl.setHost(orcidUrlManager.getBaseHost()); + String fullPath = null; + if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) { + fullPath = extractFullPath(n.getAuthorizationUrl().getUri()); } + notificationMapperV3.mapPermissionBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost()); } // 6. Notification Institutional Connection @@ -199,19 +204,12 @@ protected void afterMapInstitutionalConnection(NotificationInstitutionalConnecti @AfterMapping protected void afterMapInstitutionalConnectionEntity(NotificationInstitutionalConnectionEntity entity, @MappingTarget NotificationInstitutionalConnection n) { - AuthorizationUrl authUrl = n.getAuthorizationUrl(); - if (authUrl != null && authUrl.getUri() != null) { - authUrl.setPath(extractFullPath(authUrl.getUri())); - authUrl.setHost(orcidUrlManager.getBaseHost()); - } - - String providerId = entity.getAuthenticationProviderId(); - if (StringUtils.isNotBlank(providerId)) { - String idpName = identityProviderManager.retrieveIdentitifyProviderName(providerId); - n.setIdpName(idpName); - } else { - n.setIdpName(LAST_RESORT_IDENTITY_PROVIDER_NAME); + String fullPath = null; + if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) { + fullPath = extractFullPath(n.getAuthorizationUrl().getUri()); } + notificationMapperV3.mapInstitutionalBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost(), identityProviderManager, + LAST_RESORT_IDENTITY_PROVIDER_NAME); } // 7. Notification Amended @@ -228,6 +226,11 @@ protected void afterMapInstitutionalConnectionEntity(NotificationInstitutionalCo @Mapping(source = ".", target = "source") protected abstract NotificationAmended map(NotificationAmendedEntity e); + @AfterMapping + protected void afterMapAmendedEntity(NotificationAmendedEntity entity, @MappingTarget NotificationAmended n) { + notificationMapperV3.mapAmendedBtoA(entity, n); + } + // 8. Notification Find My Stuff (V3 specific) @Mapping(source = "putCode", target = "id") @Mapping(target = "dateCreated", ignore = true) @@ -247,17 +250,16 @@ protected void afterMapFindMyStuff(NotificationFindMyStuff n, @MappingTarget Not validateAndConvertToURI(authUrl); entity.setAuthorizationUrl(authUrl); } - entity.setAuthenticationProviderId(n.getServiceProviderId()); + notificationMapperV3.mapFindMyStuffAtoB(n, entity, entity.getAuthorizationUrl(), entity.getAuthorizationUrl()); } @AfterMapping protected void afterMapFindMyStuffEntity(NotificationFindMyStuffEntity entity, @MappingTarget NotificationFindMyStuff n) { - AuthorizationUrl authUrl = n.getAuthorizationUrl(); - if (authUrl != null && authUrl.getUri() != null) { - authUrl.setPath(extractFullPath(authUrl.getUri())); - authUrl.setHost(orcidUrlManager.getBaseHost()); + String fullPath = null; + if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) { + fullPath = extractFullPath(n.getAuthorizationUrl().getUri()); } - n.setServiceProviderId(entity.getAuthenticationProviderId()); + notificationMapperV3.mapFindMyStuffBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost()); } // Custom AmendedSection Enum Converters @@ -275,33 +277,6 @@ protected org.orcid.jaxb.model.v3.release.notification.amended.AmendedSection ma } } - // Notification Items Mapping - @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") - protected abstract NotificationItemEntity mapItem(Item item); - - @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") - protected abstract Item mapItem(NotificationItemEntity entity); - - @SuppressWarnings("rawtypes") - protected String mapAdditionalInfo(Map map) { - if (map == null || map.isEmpty()) return null; - return JsonUtils.convertToJsonString(map); - } - - @SuppressWarnings("rawtypes") - protected Map mapAdditionalInfo(String json) { - if (PojoUtil.isEmpty(json)) return null; - return JsonUtils.readObjectFromJsonString(json, HashMap.class); - } - // XMLGregorianCalendar <-> java.util.Date Converters protected Date xmlGregorianCalendarToDate(XMLGregorianCalendar cal) { if (cal == null) { diff --git a/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java b/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java index 1741b214190..3eb21d22592 100644 --- a/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java +++ b/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java @@ -8,8 +8,11 @@ import org.orcid.jaxb.model.common_v2.Contributor; import org.orcid.jaxb.model.common_v2.ContributorAttributes; import org.orcid.jaxb.model.common_v2.CreditName; +import org.orcid.jaxb.model.record.bulk.BulkElement; import org.orcid.jaxb.model.record_v2.Funding; import org.orcid.jaxb.model.record_v2.FundingContributor; +import org.orcid.jaxb.model.record_v2.Work; +import org.orcid.jaxb.model.record_v2.WorkBulk; import org.orcid.persistence.dao.RecordNameDao; import org.orcid.pojo.ContributorsRolesAndSequencesV2; import org.orcid.pojo.ajaxForm.PojoUtil; @@ -55,6 +58,29 @@ public void filterContributorPrivateData(Funding funding) { } } + /** + * The contributor email is write-only (work-3.0.xsd): it is never returned + * when reading a work. Unlike the Funding overload, credit names are left + * as deposited. + */ + public void filterContributorPrivateData(Work work) { + if (work != null && work.getWorkContributors() != null && work.getWorkContributors().getContributor() != null) { + for (Contributor contributor : work.getWorkContributors().getContributor()) { + contributor.setContributorEmail(null); + } + } + } + + public void filterContributorPrivateData(WorkBulk works) { + if (works != null && works.getBulk() != null) { + for (BulkElement element : works.getBulk()) { + if (element instanceof Work) { + filterContributorPrivateData((Work) element); + } + } + } + } + public List getContributorsGroupedByOrcid(List contributors, Integer maxContributorsForUI) { List contributorsRolesAndSequencesList = new ArrayList<>(); for(Contributor contributor : contributors) { diff --git a/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java b/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java index f6c4248b3cc..98305a5a18d 100644 --- a/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java +++ b/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java @@ -4,11 +4,14 @@ import org.orcid.core.contributors.roles.credit.CreditRole; import org.orcid.core.manager.v3.ProfileEntityManager; import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly; +import org.orcid.jaxb.model.record.bulk.BulkElement; import org.orcid.jaxb.model.v3.release.common.Contributor; import org.orcid.jaxb.model.v3.release.common.ContributorAttributes; import org.orcid.jaxb.model.v3.release.common.CreditName; import org.orcid.jaxb.model.v3.release.record.Funding; import org.orcid.jaxb.model.v3.release.record.FundingContributor; +import org.orcid.jaxb.model.v3.release.record.Work; +import org.orcid.jaxb.model.v3.release.record.WorkBulk; import org.orcid.pojo.ContributorsRolesAndSequences; import org.orcid.pojo.ajaxForm.PojoUtil; @@ -43,6 +46,29 @@ public void filterContributorPrivateData(Funding funding) { } } + /** + * The contributor email is write-only (work-3.0.xsd): it is never returned + * when reading a work. Unlike the Funding overload, credit names are left + * as deposited. + */ + public void filterContributorPrivateData(Work work) { + if (work != null && work.getWorkContributors() != null && work.getWorkContributors().getContributor() != null) { + for (Contributor contributor : work.getWorkContributors().getContributor()) { + contributor.setContributorEmail(null); + } + } + } + + public void filterContributorPrivateData(WorkBulk works) { + if (works != null && works.getBulk() != null) { + for (BulkElement element : works.getBulk()) { + if (element instanceof Work) { + filterContributorPrivateData((Work) element); + } + } + } + } + public List getContributorsGroupedByOrcid(List contributors, Integer maxContributorsForUI) { List contributorsRolesAndSequencesList = new ArrayList<>(); for(Contributor contributor : contributors) { diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl index d6a49feefcb..74448651abf 100644 --- a/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl +++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl @@ -19,6 +19,7 @@ <#if notificationType == 'PERMISSION'> ${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sourceId}: <#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c})<#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}) +<#if notification.items?? && notification.items.itemsByType?has_content> <#assign itemsByType=notification.items.itemsByType> <#list itemsByType?keys?sort as itemType> <@emailMacros.msg "email.common.recordsection." + itemType /> (${itemsByType[itemType]?size}) @@ -29,6 +30,10 @@ ${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sour <@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action <@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode} +<#else> +<@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action +<@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode} + <#elseif notificationType == 'AMENDED' && !verboseNotifications> <#assign amendedSection><@emailMacros.msg "email.common.recordsection." + notification.amendedSection /> <@emailMacros.msg "email.digest.hasupdated_1" /> diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl index 0e836a77555..a91034a3a41 100644 --- a/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl +++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl @@ -32,6 +32,7 @@ <#if notificationType == 'PERMISSION'>

 ${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sourceId}: <#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}) <#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c})
+ <#if notification.items??> <#assign itemsByType=notification.items.itemsByType> <#list itemsByType?keys?sort as itemType> @@ -65,6 +66,7 @@
+

<#elseif notificationType == 'AMENDED' && !verboseNotifications>

diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl index 20a0a05974c..6c2ceddd54a 100644 --- a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl +++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl @@ -39,6 +39,7 @@ ${'\n'}${emailName}<@emailMacros.space /><@emailMacros.msg "notification.digest. <#else> <#if notification.notificationIntro??>${notification.notificationIntro} ${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sourceId}: <#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c})<#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}) + <#if notification.items?? && notification.items.itemsByType?has_content> <#assign itemsByType=notification.items.itemsByType> <#list itemsByType?keys?sort as itemType> <@emailMacros.msg "email.common.recordsection." + itemType /> (${itemsByType[itemType]?size}) @@ -49,6 +50,10 @@ ${'\n'}${emailName}<@emailMacros.space /><@emailMacros.msg "notification.digest. <@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action <@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode} + <#else> + <@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action + <@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode} + <#elseif notificationType == 'AMENDED' && !verboseNotifications> <#assign amendedSection><@emailMacros.msg "email.common.recordsection." + notification.amendedSection /> diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl index 68b792b10d8..26e7d1f6276 100644 --- a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl +++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl @@ -110,6 +110,7 @@ <#else> <#if notification.notificationIntro??>

${notification.notificationIntro}

<#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c})<#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c})

+ <#if notification.items??> <#assign itemsByType=notification.items.itemsByType> <#list itemsByType?keys?sort as itemType>
@@ -125,6 +126,7 @@
+ ItemType.WORK.equals(i.getItemType())) + .findFirst().orElse(null); + assertNotNull(item1); + assertEquals("101", item1.getPutCode()); + assertEquals("Work Title", item1.getItemName()); + assertNotNull(item1.getExternalIdentifier()); + assertEquals("doi", item1.getExternalIdentifier().getType()); + assertEquals("10.1234/test", item1.getExternalIdentifier().getValue()); + assertEquals("https://doi.org/10.1234/test", item1.getExternalIdentifier().getUrl().getValue()); + assertEquals(org.orcid.jaxb.model.record_v2.Relationship.SELF, item1.getExternalIdentifier().getRelationship()); + + Item item2 = permissionNotification.getItems().getItems().stream() + .filter(i -> ItemType.EMPLOYMENT.equals(i.getItemType())) + .findFirst().orElse(null); + assertNotNull(item2); + assertEquals("102", item2.getPutCode()); + assertEquals("University of Testing", item2.getItemName()); + assertNull(item2.getExternalIdentifier()); + } + @Test public void testToNotificationPermissionEntity() { NotificationPermission notification = new NotificationPermission(); diff --git a/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java b/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java index 7b7335a99df..c871d2fb326 100644 --- a/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java +++ b/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java @@ -155,6 +155,7 @@ public void testEntityToNotificationWithSource() { Notification amendedNotification = jpaJaxbNotificationAdapter.toNotification(amendedEntity); assertNotNull(amendedNotification); assertTrue(amendedNotification instanceof NotificationAmended); + assertNotNull(((NotificationAmended) amendedNotification).getItems()); assertNotNull(amendedNotification.getSource()); assertNotNull(amendedNotification.getSource().getSourceClientId()); assertEquals(CLIENT_SOURCE_ID, amendedNotification.getSource().getSourceClientId().getPath()); @@ -169,6 +170,7 @@ public void testEntityToNotificationWithSource() { Notification permissionNotification = jpaJaxbNotificationAdapter.toNotification(addItemsEntity); assertNotNull(permissionNotification); assertTrue(permissionNotification instanceof NotificationPermission); + assertNotNull(((NotificationPermission) permissionNotification).getItems()); assertNotNull(permissionNotification.getSource()); assertNotNull(permissionNotification.getSource().getSourceClientId()); assertEquals(CLIENT_SOURCE_ID, permissionNotification.getSource().getSourceClientId().getPath()); @@ -176,6 +178,60 @@ public void testEntityToNotificationWithSource() { assertEquals("Client name", permissionNotification.getSource().getSourceName().getContent()); } + @Test + public void testToNotificationPermissionWithItems() { + NotificationAddItemsEntity addItemsEntity = new NotificationAddItemsEntity(); + addItemsEntity.setId(125L); + addItemsEntity.setNotificationType(org.orcid.jaxb.model.notification_v2.NotificationType.PERMISSION.name()); + addItemsEntity.setClientSourceId(CLIENT_SOURCE_ID); + addItemsEntity.setAuthorizationUrl("https://orcid.org/oauth/authorize?client_id=APP-1234"); + + NotificationItemEntity itemEntity1 = new NotificationItemEntity(); + itemEntity1.setId(101L); + itemEntity1.setItemType("WORK"); + itemEntity1.setItemName("Work Title"); + itemEntity1.setExternalIdType("DOI"); + itemEntity1.setExternalIdValue("10.1234/test"); + itemEntity1.setExternalIdUrl("https://doi.org/10.1234/test"); + itemEntity1.setExternalIdRelationship("SELF"); + + NotificationItemEntity itemEntity2 = new NotificationItemEntity(); + itemEntity2.setId(102L); + itemEntity2.setItemType("EMPLOYMENT"); + itemEntity2.setItemName("University of Testing"); + + addItemsEntity.setNotificationItems(java.util.Set.of(itemEntity1, itemEntity2)); + + Notification notification = jpaJaxbNotificationAdapter.toNotification(addItemsEntity); + assertNotNull(notification); + assertTrue(notification instanceof NotificationPermission); + NotificationPermission permissionNotification = (NotificationPermission) notification; + assertNotNull(permissionNotification.getItems()); + assertNotNull(permissionNotification.getItems().getItems()); + assertEquals(2, permissionNotification.getItems().getItems().size()); + assertEquals(2, permissionNotification.getItems().getItemsByType().size()); + + Item item1 = permissionNotification.getItems().getItems().stream() + .filter(i -> ItemType.WORK.equals(i.getItemType())) + .findFirst().orElse(null); + assertNotNull(item1); + assertEquals("101", item1.getPutCode()); + assertEquals("Work Title", item1.getItemName()); + assertNotNull(item1.getExternalIdentifier()); + assertEquals("doi", item1.getExternalIdentifier().getType()); + assertEquals("10.1234/test", item1.getExternalIdentifier().getValue()); + assertEquals("https://doi.org/10.1234/test", item1.getExternalIdentifier().getUrl().getValue()); + assertEquals(Relationship.SELF, item1.getExternalIdentifier().getRelationship()); + + Item item2 = permissionNotification.getItems().getItems().stream() + .filter(i -> ItemType.EMPLOYMENT.equals(i.getItemType())) + .findFirst().orElse(null); + assertNotNull(item2); + assertEquals("102", item2.getPutCode()); + assertEquals("University of Testing", item2.getItemName()); + assertNull(item2.getExternalIdentifier()); + } + @Test public void testToNotificationPermissionEntity() { NotificationPermission notification = new NotificationPermission(); diff --git a/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java b/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java index 39864ed1dde..567eec90471 100644 --- a/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java +++ b/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java @@ -25,14 +25,18 @@ import org.orcid.core.manager.ProfileEntityCacheManager; import org.orcid.core.manager.ProfileEntityManager; import org.orcid.jaxb.model.common_v2.Contributor; +import org.orcid.jaxb.model.common_v2.ContributorAttributes; import org.orcid.jaxb.model.common_v2.ContributorEmail; import org.orcid.jaxb.model.common_v2.ContributorOrcid; +import org.orcid.jaxb.model.common_v2.ContributorRole; import org.orcid.jaxb.model.common_v2.CreditName; import org.orcid.jaxb.model.common_v2.Title; +import org.orcid.jaxb.model.error_v2.OrcidError; import org.orcid.jaxb.model.record_v2.Funding; import org.orcid.jaxb.model.record_v2.FundingContributor; import org.orcid.jaxb.model.record_v2.FundingContributors; import org.orcid.jaxb.model.record_v2.FundingTitle; +import org.orcid.jaxb.model.record_v2.SequenceType; import org.orcid.jaxb.model.record_v2.Work; import org.orcid.jaxb.model.record_v2.WorkBulk; import org.orcid.jaxb.model.record_v2.WorkContributors; @@ -131,6 +135,60 @@ public void testFilterContributorPrivateDataForFundingWithoutContributors() { assertNotNull(funding); // test no failures } + @Test + public void testFilterContributorPrivateDataForWork() { + // A public name is available, so a name replacement would be visible + when(profileEntityManager.orcidExists(anyString())).thenReturn(true); + when(cacheManager.getPublicCreditName(any(String.class))).thenReturn("a public name"); + + Work work = getWorkWithOrcidContributor(); + ContributorAttributes attributes = new ContributorAttributes(); + attributes.setContributorRole(ContributorRole.AUTHOR); + attributes.setContributorSequence(SequenceType.FIRST); + work.getWorkContributors().getContributor().get(0).setContributorAttributes(attributes); + + contributorUtils.filterContributorPrivateData(work); + + Contributor contributor = work.getWorkContributors().getContributor().get(0); + assertNull(contributor.getContributorEmail()); + assertEquals("original credit name", contributor.getCreditName().getContent()); + assertEquals("0000-0003-4902-6327", contributor.getContributorOrcid().getPath()); + assertEquals(ContributorRole.AUTHOR, contributor.getContributorAttributes().getContributorRole()); + assertEquals(SequenceType.FIRST, contributor.getContributorAttributes().getContributorSequence()); + } + + @Test + public void testFilterContributorPrivateDataForWorkWithNoOrcidRecord() { + Work work = getWorkWithContributorWithoutOrcid(); + contributorUtils.filterContributorPrivateData(work); + + Contributor contributor = work.getWorkContributors().getContributor().get(0); + assertNull(contributor.getContributorEmail()); + assertEquals("original credit name", contributor.getCreditName().getContent()); + } + + @Test + public void testFilterContributorPrivateDataForWorkWithoutContributors() { + Work work = getWorkWithoutContributors(); + contributorUtils.filterContributorPrivateData(work); + assertNull(work.getWorkContributors()); + } + + @Test + public void testFilterContributorPrivateDataForBulkWork() { + OrcidError error = new OrcidError(); + error.setDeveloperMessage("not a work"); + WorkBulk bulk = new WorkBulk(); + bulk.setBulk(new ArrayList<>(Arrays.asList(getWorkWithOrcidContributor(), error, getWorkWithContributorWithoutOrcid()))); + + contributorUtils.filterContributorPrivateData(bulk); + + assertEquals(3, bulk.getBulk().size()); + assertNull(((Work) bulk.getBulk().get(0)).getWorkContributors().getContributor().get(0).getContributorEmail()); + assertEquals("not a work", ((OrcidError) bulk.getBulk().get(1)).getDeveloperMessage()); + assertNull(((Work) bulk.getBulk().get(2)).getWorkContributors().getContributor().get(0).getContributorEmail()); + } + private Work getWorkWithoutContributors() { Work work = new Work(); WorkTitle workTitle = new WorkTitle(); diff --git a/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java b/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java index 00dcaeb5445..38597193ef7 100644 --- a/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java +++ b/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java @@ -25,11 +25,14 @@ import org.orcid.core.manager.v3.ActivityManager; import org.orcid.core.manager.v3.ProfileEntityManager; import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly; +import org.orcid.jaxb.model.common.SequenceType; import org.orcid.jaxb.model.v3.release.common.Contributor; +import org.orcid.jaxb.model.v3.release.common.ContributorAttributes; import org.orcid.jaxb.model.v3.release.common.ContributorEmail; import org.orcid.jaxb.model.v3.release.common.ContributorOrcid; import org.orcid.jaxb.model.v3.release.common.CreditName; import org.orcid.jaxb.model.v3.release.common.Title; +import org.orcid.jaxb.model.v3.release.error.OrcidError; import org.orcid.jaxb.model.v3.release.record.Funding; import org.orcid.jaxb.model.v3.release.record.FundingContributor; import org.orcid.jaxb.model.v3.release.record.FundingContributors; @@ -130,6 +133,60 @@ public void testFilterContributorPrivateDataForFundingWithoutContributors() { assertNotNull(funding); // test no failures } + @Test + public void testFilterContributorPrivateDataForWork() { + // A public name is available, so a name replacement would be visible + when(profileEntityManager.orcidExists(anyString())).thenReturn(true); + when(recordNameManagerReadOnlyV3.fetchDisplayablePublicName(anyString())).thenReturn("a public name"); + + Work work = getWorkWithOrcidContributor(); + ContributorAttributes attributes = new ContributorAttributes(); + attributes.setContributorRole("author"); + attributes.setContributorSequence(SequenceType.FIRST); + work.getWorkContributors().getContributor().get(0).setContributorAttributes(attributes); + + contributorUtils.filterContributorPrivateData(work); + + Contributor contributor = work.getWorkContributors().getContributor().get(0); + assertNull(contributor.getContributorEmail()); + assertEquals("original credit name", contributor.getCreditName().getContent()); + assertEquals("0000-0003-4902-6327", contributor.getContributorOrcid().getPath()); + assertEquals("author", contributor.getContributorAttributes().getContributorRole()); + assertEquals(SequenceType.FIRST, contributor.getContributorAttributes().getContributorSequence()); + } + + @Test + public void testFilterContributorPrivateDataForWorkWithNoOrcidRecord() { + Work work = getWorkWithContributorWithoutOrcid(); + contributorUtils.filterContributorPrivateData(work); + + Contributor contributor = work.getWorkContributors().getContributor().get(0); + assertNull(contributor.getContributorEmail()); + assertEquals("original credit name", contributor.getCreditName().getContent()); + } + + @Test + public void testFilterContributorPrivateDataForWorkWithoutContributors() { + Work work = getWorkWithoutContributors(); + contributorUtils.filterContributorPrivateData(work); + assertNull(work.getWorkContributors()); + } + + @Test + public void testFilterContributorPrivateDataForBulkWork() { + OrcidError error = new OrcidError(); + error.setDeveloperMessage("not a work"); + WorkBulk bulk = new WorkBulk(); + bulk.setBulk(new ArrayList<>(Arrays.asList(getWorkWithOrcidContributor(), error, getWorkWithContributorWithoutOrcid()))); + + contributorUtils.filterContributorPrivateData(bulk); + + assertEquals(3, bulk.getBulk().size()); + assertNull(((Work) bulk.getBulk().get(0)).getWorkContributors().getContributor().get(0).getContributorEmail()); + assertEquals("not a work", ((OrcidError) bulk.getBulk().get(1)).getDeveloperMessage()); + assertNull(((Work) bulk.getBulk().get(2)).getWorkContributors().getContributor().get(0).getContributorEmail()); + } + private Work getWorkWithoutContributors() { Work work = new Work(); WorkTitle workTitle = new WorkTitle(); diff --git a/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java b/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java index 11d0ac2b613..ba95e030c93 100644 --- a/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java +++ b/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java @@ -100,9 +100,8 @@ public void updateLastModified(String clientId) { /** * Update the last modified dates of given client ids - * - * @param clientIds - * A list of client ids + * + * @param clientIds A list of client ids * @return the amount of modified rows */ @@ -194,9 +193,8 @@ public void removeClient(String clientId) { /** * Get the public client that belongs to the given orcid ID - * - * @param ownerId - * The user or group id + * + * @param ownerId The user or group id * @return the public client that belongs to the given user */ @Override @@ -216,9 +214,8 @@ public ClientDetailsEntity getPublicClient(String ownerId) { /** * Get member name - * - * @param clientId - * The client id + * + * @param clientId The client id * @return the name of the member owner of the given client */ @Override @@ -269,7 +266,7 @@ public List findLegacyClientIds() { @Transactional public void changePersistenceTokensProperty(String clientId, boolean isPersistenTokensEnabled) { Query updateQuery = entityManager - .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.persistentTokensEnabled = :isPersistenTokensEnabled where c.id = :clientId"); + .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.persistentTokensEnabled = :isPersistenTokensEnabled where c.id = :clientId"); updateQuery.setParameter("clientId", clientId); updateQuery.setParameter("lastModified", new Date()); updateQuery.setParameter("isPersistenTokensEnabled", isPersistenTokensEnabled); @@ -280,7 +277,7 @@ public void changePersistenceTokensProperty(String clientId, boolean isPersisten @Transactional public void activateClient(String clientDetailsId) { Query updateQuery = entityManager - .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = null, c.deactivatedBy = null where c.id = :clientId"); + .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = null, c.deactivatedBy = null where c.id = :clientId"); updateQuery.setParameter("clientId", clientDetailsId); updateQuery.setParameter("lastModified", new Date()); updateQuery.executeUpdate(); @@ -290,7 +287,7 @@ public void activateClient(String clientDetailsId) { @Transactional public void deactivateClient(String clientDetailsId, String deactivatedBy) { Query updateQuery = entityManager - .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = :deactivatedDate, c.deactivatedBy = :deactivatedBy where c.id = :clientId"); + .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = :deactivatedDate, c.deactivatedBy = :deactivatedBy where c.id = :clientId"); updateQuery.setParameter("clientId", clientDetailsId); Date now = new Date(); updateQuery.setParameter("lastModified", now); @@ -324,7 +321,7 @@ public boolean updateClientGrantedAuthority(String clientId, String grantedAutho @Transactional public boolean updateNotificationInfo(String clientId, boolean userNotificationEnabled, String notificationWebpageUrl, String notificationDomains) { Query updateNotificationInfoQuery = entityManager.createNativeQuery( - "UPDATE client_details SET user_notification_enabled= :userNotificationEnabled, notification_webpage_url= :notificationWebpageUrl, notification_domains= :notificationDomains WHERE id = :clientId"); + "UPDATE client_details SET user_notification_enabled= :userNotificationEnabled, notification_webpage_url= :notificationWebpageUrl, notification_domains= :notificationDomains WHERE client_details_id = :clientId"); updateNotificationInfoQuery.setParameter("clientId", clientId); updateNotificationInfoQuery.setParameter("userNotificationEnabled", userNotificationEnabled); updateNotificationInfoQuery.setParameter("notificationWebpageUrl", notificationWebpageUrl); @@ -336,7 +333,7 @@ public boolean updateNotificationInfo(String clientId, boolean userNotificationE @Transactional(value = "transactionManagerReadOnly", readOnly = true) @SuppressWarnings("unchecked") public List findMVPEnabled() { - Query query = entityManager.createQuery("from ClientDetailsEntity where userNotificationEnabled = :userNotificationEnabled and client_type = :premiumUpdater" ); + Query query = entityManager.createQuery("from ClientDetailsEntity where userNotificationEnabled = :userNotificationEnabled and clientType = :premiumUpdater"); query.setParameter("userNotificationEnabled", true); query.setParameter("premiumUpdater", ClientType.PREMIUM_UPDATER.name()); return query.getResultList(); diff --git a/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java b/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java index b784aa911dc..d5b514a993e 100644 --- a/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java +++ b/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java @@ -1,6 +1,8 @@ package org.orcid.persistence.dao; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; @@ -12,12 +14,14 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.orcid.jaxb.model.clientgroup.ClientType; +import org.orcid.persistence.jpa.entities.ClientDetailsEntity; import org.orcid.test.DBUnitTest; import org.orcid.test.OrcidJUnit4ClassRunner; import org.springframework.test.context.ContextConfiguration; @RunWith(OrcidJUnit4ClassRunner.class) -@ContextConfiguration(locations = { "classpath:test-orcid-persistence-context.xml" }) +@ContextConfiguration(locations = {"classpath:test-orcid-persistence-context.xml"}) public class ClientDetailsDaoTest extends DBUnitTest { private static String CLIENT_ID = "APP-5555555555555557"; @@ -48,4 +52,16 @@ public void testUpdateLastModifiedBulk() { assertEquals(updateClients, 2); } + @Test + public void testFindMVPEnabled() { + clientDetailsDao.updateClientType(ClientType.PREMIUM_UPDATER.name(), CLIENT_ID); + clientDetailsDao.updateNotificationInfo(CLIENT_ID, true, "http://test.com", "test.com"); + List mvpEnabledClients = clientDetailsDao.findMVPEnabled(); + assertNotNull(mvpEnabledClients); + assertEquals(1, mvpEnabledClients.size()); + assertEquals(CLIENT_ID, mvpEnabledClients.get(0).getId()); + assertEquals(ClientType.PREMIUM_UPDATER.name(), mvpEnabledClients.get(0).getClientType()); + assertTrue(mvpEnabledClients.get(0).isUserNotificationEnabled()); + } + } diff --git a/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java b/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java index ae7c00a46ff..4b45bce7359 100644 --- a/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java +++ b/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java @@ -207,6 +207,7 @@ public Response viewActivities(String orcid) { public Response viewWork(String orcid, Long putCode) { Work w = workManagerReadOnly.getWork(orcid, putCode); publicAPISecurityManagerV2.checkIsPublic(w); + contributorUtilsReadOnly.filterContributorPrivateData(w); ActivityUtils.cleanEmptyFields(w); ActivityUtils.setPathToActivity(w, orcid); @@ -557,6 +558,7 @@ public Response viewBulkWorks(String orcid, String putCodes) { } WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes); publicAPISecurityManagerV2.filter(workBulk); + contributorUtilsReadOnly.filterContributorPrivateData(workBulk); ActivityUtils.cleanEmptyFields(workBulk); ActivityUtils.setPathToBulk(workBulk, orcid); sourceUtils.setSourceName(workBulk); diff --git a/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java b/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java index 124ecbd44aa..722be3d0cfc 100644 --- a/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java +++ b/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java @@ -246,6 +246,7 @@ public Response viewWork(String orcid, Long putCode) { checkProfileStatus(orcid); Work w = workManagerReadOnly.getWork(orcid, putCode); publicAPISecurityManagerV3.checkIsPublic(w); + contributorUtilsReadOnly.filterContributorPrivateData(w); ActivityUtils.cleanEmptyFields(w); ActivityUtils.setPathToActivity(w, orcid); sourceUtilsReadOnly.setSourceName(w); @@ -645,6 +646,7 @@ public Response viewBulkWorks(String orcid, String putCodes) { } WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes); publicAPISecurityManagerV3.filter(workBulk); + contributorUtilsReadOnly.filterContributorPrivateData(workBulk); ActivityUtils.cleanEmptyFields(workBulk); ActivityUtils.setPathToBulk(workBulk, orcid); sourceUtils.setSourceName(workBulk); diff --git a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java index e5a73d9f004..e34f4d971d0 100644 --- a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java +++ b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java @@ -155,6 +155,7 @@ public void testViewWork() { 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()); } @Test @@ -205,6 +206,7 @@ public void testViewBulkWorks() { 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()); } @Test diff --git a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java index 5dceaabc87c..d32679e6c56 100644 --- a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java +++ b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java @@ -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; @@ -990,6 +991,25 @@ public void testViewPersonAndRecordKeepVerifiedAndPrimaryOnV2_1() { assertEmailFlags(record.getPerson().getEmails().getEmails()); } + /** + * 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() { + assertNoContributorEmail((Work) serviceDelegator.viewWork(userWithPublicEmails, 11L).getEntity()); + assertNoContributorEmail((Work) serviceDelegatorV2_1.viewWork(userWithPublicEmails, 11L).getEntity()); + assertNoContributorEmail((Work) ((WorkBulk) serviceDelegator.viewBulkWorks(userWithPublicEmails, "11").getEntity()).getBulk().get(0)); + assertNoContributorEmail((Work) ((WorkBulk) serviceDelegatorV2_1.viewBulkWorks(userWithPublicEmails, "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()); + } + /** * Compares values rather than asserting true: a dropped flag is null, and false has to stay * false rather than collapse to null. diff --git a/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java b/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java index f1bd4e59461..9757db70723 100644 --- a/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java +++ b/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java @@ -183,6 +183,7 @@ public void testViewWork() { 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()); } @Test @@ -233,6 +234,7 @@ public void testViewBulkWorks() { 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()); } @Test diff --git a/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java b/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java index 5056cdb734d..533e4155c4a 100644 --- a/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java +++ b/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java @@ -187,6 +187,32 @@ public void digestDoesNotEvaluateStoredNotificationBody() { assertTrue("stored markup should still render", html.contains("

Hello from ")); } + @Test + public void testPermissionNotificationEmail() { + NotificationPermission notification = new NotificationPermission(); + notification.setPutCode(100L); + notification.setNotificationType(NotificationType.PERMISSION); + notification.setSubject("Connect with your organization"); + notification.setNotificationSubject("Connect with your organization"); + notification.setNotificationIntro("University of Testing"); + notification.setAuthorizationUrl(new AuthorizationUrl("https://orcid.org/oauth/authorize?client_id=APP-1234")); + notification.setCreatedDate(DateUtils.convertToXMLGregorianCalendar("2026-10-01T12:00:00")); + Source source = new Source(); + source.setSourceName(new SourceName("University of Testing")); + source.setSourceClientId(new SourceClientId("APP-1234567890123456")); + notification.setSource(source); + + List notifications = new ArrayList<>(); + notifications.add(notification); + + EmailMessage emailMessage = emailMessageSender.createDigest("0000-0000-0000-0000", notifications); + assertNotNull(emailMessage); + assertNotNull(emailMessage.getBodyHtml()); + assertNotNull(emailMessage.getBodyText()); + assertTrue(emailMessage.getBodyHtml().contains("University of Testing")); + assertTrue(emailMessage.getBodyText().contains("University of Testing")); + } + private List generateNotifications() { List notifications = new ArrayList<>(); diff --git a/orcid-test/src/main/resources/data/WorksEntityData.xml b/orcid-test/src/main/resources/data/WorksEntityData.xml index 56d886530fa..97e383b79b0 100644 --- a/orcid-test/src/main/resources/data/WorksEntityData.xml +++ b/orcid-test/src/main/resources/data/WorksEntityData.xml @@ -180,7 +180,7 @@ added_to_profile_date="2016-04-01 11:09:28.636" visibility="PUBLIC" external_ids_json="{"workExternalIdentifier":[{"workExternalIdentifierType":"DOI","relationship":"self","workExternalIdentifierId":{"content":"1"}}],"scope":null}" - contributors_json="{"contributor":[{"contributorOrcid":{"path":"0000-0000-0000-0000"},"creditName":{"content":null},"contributorEmail":null,"contributorAttributes":null}]}" + contributors_json="{"contributor":[{"contributorOrcid":{"path":"0000-0000-0000-0000"},"creditName":{"content":null},"contributorEmail":{"value":"never@show.this"},"contributorAttributes":null}]}" display_index="0" work_url="http://testuri.org" featured_display_index="0" diff --git a/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java b/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java index 262b5e1b70f..57547d94e77 100644 --- a/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java +++ b/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; import org.orcid.authorization.authentication.MFAWebAuthenticationDetails; import org.orcid.frontend.web.util.MFAWebAuthenticationDetailsDeserializer; import org.orcid.frontend.web.util.SwitchUserGrantedAuthorityDeserializer; @@ -34,6 +35,16 @@ private ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModules(SecurityJackson2Modules.getModules(this.loader)); mapper.registerModules(new CoreJackson2Module()); + mapper.activateDefaultTyping( + BasicPolymorphicTypeValidator.builder() + .allowIfSubType("java.lang") + .allowIfSubType("org.orcid") + .allowIfSubType("org.springframework.security") + .allowIfSubType("jakarta.servlet") + .allowIfSubType("java.util") + .build(), + ObjectMapper.DefaultTyping.NON_FINAL, + JsonTypeInfo.As.PROPERTY); mapper.addMixIn(String[].class, StringArrayMixin.class); mapper.addMixIn(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixin.class); mapper.addMixIn(MFAWebAuthenticationDetails.class, MFAWebAuthenticationDetailsMixin.class); diff --git a/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java b/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java index c3084ba9806..2f1640593aa 100644 --- a/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java +++ b/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java @@ -17,12 +17,16 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.orcid.frontend.spring.configuration.OrcidBeanClassLoaderAware; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.web.context.request.RequestContextHolder; +import static org.junit.Assert.assertEquals; + @RunWith(MockitoJUnitRunner.class) public class OrcidRedisIndexedSessionRepositoryTest { @@ -83,4 +87,19 @@ public void deleteByIdShouldWorkWithoutHttpRequestContext() { verify(principalSetOperations).remove(eq(SESSION_ID)); verify(expirationSetOperations).remove(eq("expires:" + SESSION_ID)); } + + @Test + public void springSessionSerializerShouldAllowJavaLongsForSessionDelta() { + OrcidBeanClassLoaderAware beanClassLoaderAware = new OrcidBeanClassLoaderAware(); + beanClassLoaderAware.setBeanClassLoader(getClass().getClassLoader()); + + RedisSerializer serializer = beanClassLoaderAware.springSessionDefaultRedisSerializer(); + Map sessionDelta = new HashMap<>(); + sessionDelta.put("lastAccessedTime", 123456789L); + + byte[] json = serializer.serialize(sessionDelta); + Map deserialized = (Map) serializer.deserialize(json); + + assertEquals(Long.valueOf(123456789L), deserialized.get("lastAccessedTime")); + } }