This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch 4_0_X in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/4_0_X by this push: new 8f29203b9d [SYNCOPE-1889] improve group search using SCIM extension (#1127) 8f29203b9d is described below commit 8f29203b9d5ad8ecccdbc218c2ab7d2fc1b6a79f Author: Samuel Garofalo <72073457+samuelg...@users.noreply.github.com> AuthorDate: Fri Jul 4 13:52:18 2025 +0200 [SYNCOPE-1889] improve group search using SCIM extension (#1127) --- .../syncope/core/persistence/api/dao/GroupDAO.java | 3 +- .../common/dao/AbstractAnyMatchDAO.java | 3 +- .../persistence/jpa/dao/repo/GroupRepoExt.java | 3 +- .../persistence/jpa/dao/repo/GroupRepoExtImpl.java | 12 ++++++-- .../core/persistence/jpa/outer/GroupTest.java | 3 +- .../core/persistence/jpa/outer/UserTest.java | 6 ++-- .../persistence/neo4j/dao/repo/GroupRepoExt.java | 3 +- .../neo4j/dao/repo/GroupRepoExtImpl.java | 13 +++++++-- .../core/persistence/neo4j/outer/GroupTest.java | 3 +- .../core/persistence/neo4j/outer/UserTest.java | 6 ++-- .../java/data/GroupDataBinderImpl.java | 3 +- .../java/pushpull/LDAPMembershipPullActions.java | 3 +- .../pushpull/LDAPMembershipPullActionsTest.java | 5 +++- .../elasticsearch/client/ElasticsearchUtils.java | 3 +- .../flowable/support/SyncopeUserQueryImpl.java | 3 +- .../ext/opensearch/client/OpenSearchUtils.java | 3 +- .../apache/syncope/core/logic/SCIMDataBinder.java | 32 +++++++++++++--------- .../syncope/core/logic/SCIMLogicContext.java | 6 ++-- .../syncope/core/logic/SCIMDataBinderTest.java | 4 ++- .../org/apache/syncope/fit/core/SCIMITCase.java | 32 ++++++++++++++++++++++ 20 files changed, 111 insertions(+), 38 deletions(-) diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java index bae211073c..075afed7b0 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java @@ -31,6 +31,7 @@ import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.springframework.data.domain.Pageable; public interface GroupDAO extends AnyDAO<Group> { @@ -58,7 +59,7 @@ public interface GroupDAO extends AnyDAO<Group> { List<AMembership> findAMemberships(Group group); - List<UMembership> findUMemberships(Group group); + List<UMembership> findUMemberships(Group group, Pageable pageable); List<String> findAMembers(String groupKey); diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java index b511bc163c..eadd640bc1 100644 --- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java @@ -67,6 +67,7 @@ import org.apache.syncope.core.persistence.api.entity.user.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; public abstract class AbstractAnyMatchDAO implements AnyMatchDAO { @@ -274,7 +275,7 @@ public abstract class AbstractAnyMatchDAO implements AnyMatchDAO { || groupDAO.findADynMembers(group).contains(cond.getMember()); } } else { - found = groupDAO.findUMemberships(group).stream(). + found = groupDAO.findUMemberships(group, Pageable.unpaged()).stream(). anyMatch(memb -> memb.getLeftEnd().getKey().equals(cond.getMember())) || groupDAO.findUDynMembers(group).contains(cond.getMember()); } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java index cc7004b39a..0ff0c94649 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java @@ -29,6 +29,7 @@ import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.springframework.data.domain.Pageable; public interface GroupRepoExt extends AnyRepoExt<Group> { @@ -48,7 +49,7 @@ public interface GroupRepoExt extends AnyRepoExt<Group> { List<AMembership> findAMemberships(Group group); - List<UMembership> findUMemberships(Group group); + List<UMembership> findUMemberships(Group group, Pageable pageable); Group saveAndRefreshDynMemberships(Group group); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java index e06e96a078..427757ca23 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java @@ -69,6 +69,7 @@ import org.apache.syncope.core.spring.security.DelegatedAdministrationException; import org.identityconnectors.framework.common.objects.SyncDeltaType; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group> implements GroupRepoExt { @@ -214,11 +215,16 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group> implements Group } @Override - public List<UMembership> findUMemberships(final Group group) { + public List<UMembership> findUMemberships(final Group group, final Pageable pageable) { TypedQuery<UMembership> query = entityManager.createQuery( - "SELECT e FROM " + JPAUMembership.class.getSimpleName() + " e WHERE e.rightEnd=:group", + "SELECT e FROM " + JPAUMembership.class.getSimpleName() + + " e WHERE e.rightEnd=:group ORDER BY e.leftEnd", UMembership.class); query.setParameter("group", group); + if (pageable.isPaged()) { + query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber()); + query.setMaxResults(pageable.getPageSize()); + } return query.getResultList(); } @@ -308,7 +314,7 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group> implements Group new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, leftEnd, AuthContextUtils.getDomain())); }); - findUMemberships(group).forEach(membership -> { + findUMemberships(group, Pageable.unpaged()).forEach(membership -> { User leftEnd = membership.getLeftEnd(); leftEnd.remove(membership); membership.setRightEnd(null); diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java index fbf58712d5..f41d052cbd 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java @@ -61,6 +61,7 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMemb import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.test.annotation.DirtiesContext; import org.springframework.transaction.annotation.Transactional; @@ -115,7 +116,7 @@ public class GroupTest extends AbstractTest { @Test public void uMemberships() { List<UMembership> memberships = groupDAO.findUMemberships( - groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow()); + groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow(), Pageable.unpaged()); assertEquals(2, memberships.size()); assertTrue(memberships.stream().anyMatch(m -> "3d5e91f6-305e-45f9-ad30-4897d3d43bd9".equals(m.getKey()))); assertTrue(memberships.stream().anyMatch(m -> "d53f7657-2b22-4e10-a2cd-c3379a4d1a31".equals(m.getKey()))); diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java index cda04cc3ae..b4bb2186df 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java @@ -51,6 +51,7 @@ import org.apache.syncope.core.persistence.jpa.AbstractTest; import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.test.annotation.DirtiesContext; import org.springframework.transaction.annotation.Transactional; @@ -91,7 +92,7 @@ public class UserTest extends AbstractTest { @Test public void delete() { List<UMembership> memberships = groupDAO.findUMemberships( - groupDAO.findByName("managingDirector").orElseThrow()); + groupDAO.findByName("managingDirector").orElseThrow(), Pageable.unpaged()); assertFalse(memberships.isEmpty()); userDAO.deleteById("c9b2dec2-00a7-4855-97c0-d854842b4b24"); @@ -101,7 +102,8 @@ public class UserTest extends AbstractTest { assertTrue(userDAO.findByUsername("bellini").isEmpty()); assertTrue(plainSchemaDAO.findById("loginDate").isPresent()); - memberships = groupDAO.findUMemberships(groupDAO.findByName("managingDirector").orElseThrow()); + memberships = groupDAO.findUMemberships( + groupDAO.findByName("managingDirector").orElseThrow(), Pageable.unpaged()); assertTrue(memberships.isEmpty()); } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java index 8486469408..3aace7086e 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java @@ -29,6 +29,7 @@ import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.group.TypeExtension; import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.springframework.data.domain.Pageable; public interface GroupRepoExt extends AnyRepoExt<Group> { @@ -48,7 +49,7 @@ public interface GroupRepoExt extends AnyRepoExt<Group> { List<AMembership> findAMemberships(Group group); - List<UMembership> findUMemberships(Group group); + List<UMembership> findUMemberships(Group group, Pageable pageable); Group saveAndRefreshDynMemberships(Group group); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java index 4aa022737d..1d537d2816 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java @@ -80,6 +80,7 @@ import org.apache.syncope.core.spring.security.DelegatedAdministrationException; import org.identityconnectors.framework.common.objects.SyncDeltaType; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.transaction.annotation.Transactional; @@ -260,11 +261,17 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group, Neo4jGroup> impl } @Override - public List<UMembership> findUMemberships(final Group group) { + public List<UMembership> findUMemberships(final Group group, final Pageable pageable) { + String paged = ""; + if (pageable.isPaged()) { + paged = " SKIP " + pageable.getPageSize() * pageable.getPageNumber() + + " LIMIT " + pageable.getPageSize(); + } return toList( neo4jClient.query( "MATCH (n:" + Neo4jUMembership.NODE + ")-[]-(g:" + Neo4jGroup.NODE + " {id: $id}) " - + "RETURN n.id").bindAll(Map.of("id", group.getKey())).fetch().all(), + + "RETURN n.id" + paged) + .bindAll(Map.of("id", group.getKey())).fetch().all(), "n.id", Neo4jUMembership.class, null); @@ -406,7 +413,7 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group, Neo4jGroup> impl new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, leftEnd, AuthContextUtils.getDomain())); }); - findUMemberships(group).forEach(membership -> { + findUMemberships(group, Pageable.unpaged()).forEach(membership -> { User leftEnd = membership.getLeftEnd(); leftEnd.remove(membership); membership.setRightEnd(null); diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java index 84d0772608..ef74532e6d 100644 --- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java @@ -57,6 +57,7 @@ import org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jADynGroup import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUDynGroupMembership; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.transaction.annotation.Transactional; @@ -113,7 +114,7 @@ public class GroupTest extends AbstractTest { @Test public void uMemberships() { List<UMembership> memberships = groupDAO.findUMemberships( - groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow()); + groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow(), Pageable.unpaged()); assertEquals(2, memberships.size()); assertTrue(memberships.stream().anyMatch(m -> "3d5e91f6-305e-45f9-ad30-4897d3d43bd9".equals(m.getKey()))); assertTrue(memberships.stream().anyMatch(m -> "d53f7657-2b22-4e10-a2cd-c3379a4d1a31".equals(m.getKey()))); diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java index 3499d17a63..9f596eb402 100644 --- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java @@ -49,6 +49,7 @@ import org.apache.syncope.core.persistence.neo4j.AbstractTest; import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jLinkedAccount; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.transaction.annotation.Transactional; @@ -91,7 +92,7 @@ public class UserTest extends AbstractTest { @Test public void delete() { List<UMembership> memberships = groupDAO.findUMemberships( - groupDAO.findByName("managingDirector").orElseThrow()); + groupDAO.findByName("managingDirector").orElseThrow(), Pageable.unpaged()); assertFalse(memberships.isEmpty()); userDAO.deleteById("c9b2dec2-00a7-4855-97c0-d854842b4b24"); @@ -99,7 +100,8 @@ public class UserTest extends AbstractTest { assertTrue(userDAO.findByUsername("bellini").isEmpty()); assertTrue(plainSchemaDAO.findById("loginDate").isPresent()); - memberships = groupDAO.findUMemberships(groupDAO.findByName("managingDirector").orElseThrow()); + memberships = groupDAO.findUMemberships( + groupDAO.findByName("managingDirector").orElseThrow(), Pageable.unpaged()); assertTrue(memberships.isEmpty()); } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java index e8c11d92ce..9ede39b550 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java @@ -75,6 +75,7 @@ import org.apache.syncope.core.provisioning.api.MappingManager; import org.apache.syncope.core.provisioning.api.PropagationByResource; import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; @Transactional(rollbackFor = { Throwable.class }) @@ -580,7 +581,7 @@ public class GroupDataBinderImpl extends AnyDataBinder implements GroupDataBinde Map<String, PropagationByResource<String>> result = new HashMap<>(); - groupDAO.findUMemberships(group). + groupDAO.findUMemberships(group, Pageable.unpaged()). forEach((membership) -> populateTransitiveResources(group, membership.getLeftEnd(), result)); return result; diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java index 11d4c67d74..51edb543ee 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java @@ -52,6 +52,7 @@ import org.identityconnectors.framework.common.objects.LiveSyncDelta; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -129,7 +130,7 @@ public class LDAPMembershipPullActions implements InboundActions { } groupDAO.findUMemberships(groupDAO.findById(entity.getKey()). - orElseThrow(() -> new NotFoundException("Group " + entity.getKey()))). + orElseThrow(() -> new NotFoundException("Group " + entity.getKey())), Pageable.unpaged()). forEach(uMembership -> { Set<String> memb = membershipsBefore.computeIfAbsent( uMembership.getLeftEnd().getKey(), diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java index a5852e59a0..f7138cc600 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -68,6 +69,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.test.util.ReflectionTestUtils; public class LDAPMembershipPullActionsTest extends AbstractTest { @@ -142,7 +144,8 @@ public class LDAPMembershipPullActionsTest extends AbstractTest { ReflectionTestUtils.setField(ldapMembershipPullActions, "membershipsAfter", membershipsAfter); lenient().when(groupDAO.findById(anyString())).thenAnswer(ic -> Optional.of(mock(Group.class))); - lenient().when(groupDAO.findUMemberships(any(Group.class))).thenReturn(List.of(uMembership)); + lenient().when(groupDAO.findUMemberships(any(Group.class), eq(Pageable.unpaged()))) + .thenReturn(List.of(uMembership)); ConnConfPropSchema connConfPropSchema = new ConnConfPropSchema(); connConfPropSchema.setName("testSchemaName"); diff --git a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java index 5a95a11607..1f887cd7c8 100644 --- a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java +++ b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java @@ -44,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.Role; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; /** @@ -146,7 +147,7 @@ public class ElasticsearchUtils { Optional.ofNullable(group.getGroupOwner()).ifPresent(go -> builder.put("groupOwner", go.getKey())); Set<String> members = new HashSet<>(); - members.addAll(groupDAO.findUMemberships(group).stream(). + members.addAll(groupDAO.findUMemberships(group, Pageable.unpaged()).stream(). map(membership -> membership.getLeftEnd().getKey()).toList()); members.addAll(groupDAO.findUDynMembers(group)); members.addAll(groupDAO.findAMemberships(group).stream(). diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java index 5868a97fc4..ecdf1425e6 100644 --- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java +++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java @@ -27,6 +27,7 @@ import org.flowable.idm.engine.impl.UserQueryImpl; import org.flowable.idm.engine.impl.persistence.entity.UserEntity; import org.flowable.idm.engine.impl.persistence.entity.UserEntityImpl; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; public class SyncopeUserQueryImpl extends UserQueryImpl { @@ -56,7 +57,7 @@ public class SyncopeUserQueryImpl extends UserQueryImpl { } else if (groupId != null) { groupDAO.findByName(groupId).map(group -> { List<User> r = new ArrayList<>(); - groupDAO.findUMemberships(group).stream().map(m -> fromSyncopeUser(m.getLeftEnd())). + groupDAO.findUMemberships(group, Pageable.unpaged()).stream().map(m -> fromSyncopeUser(m.getLeftEnd())). filter(user -> !r.contains(user)). forEach(r::add); return r; diff --git a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java index afdb185449..b94ee902dc 100644 --- a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java +++ b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java @@ -44,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.Role; import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; import org.apache.syncope.core.persistence.api.entity.group.Group; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; /** @@ -146,7 +147,7 @@ public class OpenSearchUtils { Optional.ofNullable(group.getGroupOwner()).ifPresent(go -> builder.put("groupOwner", go.getKey())); Set<String> members = new HashSet<>(); - members.addAll(groupDAO.findUMemberships(group).stream(). + members.addAll(groupDAO.findUMemberships(group, Pageable.unpaged()).stream(). map(membership -> membership.getLeftEnd().getKey()).toList()); members.addAll(groupDAO.findUDynMembers(group)); members.addAll(groupDAO.findAMemberships(group).stream(). diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java index e9292daa2b..389e6658d7 100644 --- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java +++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java @@ -54,8 +54,11 @@ import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.lib.types.StatusRType; import org.apache.syncope.core.logic.scim.SCIMConfManager; import org.apache.syncope.core.persistence.api.dao.AnyDAO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.dao.search.MembershipCond; import org.apache.syncope.core.persistence.api.dao.search.SearchCond; +import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.provisioning.api.jexl.JexlUtils; import org.apache.syncope.core.spring.security.AuthDataAccessor; import org.apache.syncope.ext.scimv2.api.BadRequestException; @@ -79,6 +82,7 @@ import org.apache.syncope.ext.scimv2.api.type.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; public class SCIMDataBinder { @@ -123,14 +127,18 @@ public class SCIMDataBinder { protected final AuthDataAccessor authDataAccessor; + protected final GroupDAO groupDAO; + public SCIMDataBinder( final SCIMConfManager confManager, final UserLogic userLogic, - final AuthDataAccessor authDataAccessor) { + final AuthDataAccessor authDataAccessor, + final GroupDAO groupDAO) { this.confManager = confManager; this.userLogic = userLogic; this.authDataAccessor = authDataAccessor; + this.groupDAO = groupDAO; } protected <E extends Enum<?>> void fill( @@ -991,6 +999,7 @@ public class SCIMDataBinder { return Pair.of(userUR, statusR); } + @Transactional(readOnly = true) public SCIMGroup toSCIMGroup( final GroupTO groupTO, final String location, @@ -1029,18 +1038,15 @@ public class SCIMDataBinder { long count = userLogic.search( searchCond, PageRequest.of(0, 1), SyncopeConstants.ROOT_REALM, true, false).getTotalElements(); - for (int page = 0; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE); page++) { - List<UserTO> users = userLogic.search( - searchCond, - PageRequest.of(page, AnyDAO.DEFAULT_PAGE_SIZE), - SyncopeConstants.ROOT_REALM, - true, - false). - getContent(); - users.forEach(userTO -> group.getMembers().add(new Member( - userTO.getKey(), - StringUtils.substringBefore(location, "/Groups") + "/Users/" + userTO.getKey(), - userTO.getUsername()))); + for (int page = 0; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) { + List<UMembership> users = groupDAO.findUMemberships( + groupDAO.findById(groupTO.getKey()) + .orElseThrow(() -> new NotFoundException("Group " + groupTO.getKey())), + PageRequest.of(page, AnyDAO.DEFAULT_PAGE_SIZE)); + users.forEach(uMembership -> group.getMembers().add(new Member( + uMembership.getLeftEnd().getKey(), + StringUtils.substringBefore(location, "/Groups") + "/Users/" + uMembership.getKey(), + uMembership.getLeftEnd().getUsername()))); } } diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java index 559146e273..968dd69dfc 100644 --- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java +++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java @@ -21,6 +21,7 @@ package org.apache.syncope.core.logic; import org.apache.syncope.common.keymaster.client.api.ConfParamOps; import org.apache.syncope.core.logic.init.SCIMLoader; import org.apache.syncope.core.logic.scim.SCIMConfManager; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.spring.security.AuthDataAccessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -46,9 +47,10 @@ public class SCIMLogicContext { public SCIMDataBinder scimDataBinder( final SCIMConfManager confManager, final UserLogic userLogic, - final AuthDataAccessor authDataAccessor) { + final AuthDataAccessor authDataAccessor, + final GroupDAO groupDAO) { - return new SCIMDataBinder(confManager, userLogic, authDataAccessor); + return new SCIMDataBinder(confManager, userLogic, authDataAccessor, groupDAO); } @ConditionalOnMissingBean diff --git a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java index c3f378c147..d65e4b9d63 100644 --- a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java +++ b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java @@ -27,6 +27,7 @@ import java.util.stream.Stream; import org.apache.syncope.common.lib.scim.SCIMConf; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.core.logic.scim.SCIMConfManager; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.spring.security.AuthDataAccessor; import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation; import org.apache.syncope.ext.scimv2.api.data.SCIMPatchPath; @@ -50,7 +51,8 @@ class SCIMDataBinderTest { when(scimConfManager.get()).thenReturn(new SCIMConf()); UserLogic userLogic = mock(UserLogic.class); AuthDataAccessor authDataAccessor = mock(AuthDataAccessor.class); - dataBinder = new SCIMDataBinder(scimConfManager, userLogic, authDataAccessor); + GroupDAO groupDAO = mock(GroupDAO.class); + dataBinder = new SCIMDataBinder(scimConfManager, userLogic, authDataAccessor, groupDAO); } @ParameterizedTest diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java index 999b2c8dc3..6d8625db70 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java @@ -45,6 +45,7 @@ import java.util.List; import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.request.GroupUR; import org.apache.syncope.common.lib.request.StringPatchItem; import org.apache.syncope.common.lib.request.UserUR; @@ -63,6 +64,7 @@ import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.rest.api.Preference; import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.beans.AnyQuery; import org.apache.syncope.ext.scimv2.api.SCIMConstants; import org.apache.syncope.ext.scimv2.api.data.Group; import org.apache.syncope.ext.scimv2.api.data.ListResponse; @@ -385,6 +387,36 @@ public class SCIMITCase extends AbstractITCase { SCIMGroup additional = groups.getResources().getFirst(); assertEquals("additional", additional.getDisplayName()); + List<UserTO> usersList = USER_SERVICE.search(new AnyQuery.Builder() + .realm(SyncopeConstants.ROOT_REALM) + .page(0) + .size(15) + .fiql("$groups==additional") + .build()).getResult(); + assertEquals(usersList.size(), additional.getMembers().size()); + + // eq to get members + response = webClient().path("Groups").query("filter", "displayName eq \"child\"").get(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals( + SCIMConstants.APPLICATION_SCIM_JSON, + StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); + + groups = response.readEntity(new GenericType<>() { + }); + assertNotNull(groups); + assertEquals(1, groups.getTotalResults()); + + SCIMGroup child = groups.getResources().getFirst(); + assertEquals("child", child.getDisplayName()); + usersList = USER_SERVICE.search(new AnyQuery.Builder() + .realm(SyncopeConstants.ROOT_REALM) + .page(0) + .size(15) + .fiql("$groups==child") + .build()).getResult(); + assertEquals(usersList.size(), child.getMembers().size()); + assertTrue(child.getMembers().stream().anyMatch(member -> member.getDisplay().equals("verdi"))); // eq via POST SCIMSearchRequest request = new SCIMSearchRequest("displayName eq \"additional\"", null, null, null, null);