This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit ff7de522d3c5f15ac3bcb01f78f7567213592f8a
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri Sep 25 08:23:22 2026 +0200

    [SYNCOPE-1999] Fixing managed users / groups / any objects find operations 
(#1557)
---
 .../syncope/core/persistence/api/dao/GroupDAO.java |  78 ++++--
 .../syncope/core/persistence/api/dao/UserDAO.java  |  49 ++++
 .../core/persistence/jpa/dao/repo/GroupRepo.java   |  16 --
 .../persistence/jpa/dao/repo/GroupRepoExt.java     |  26 +-
 .../persistence/jpa/dao/repo/GroupRepoExtImpl.java | 218 ++++++++++++-----
 .../persistence/jpa/dao/repo/UserRepoExtImpl.java  |  62 +++--
 .../core/persistence/jpa/outer/GroupTest.java      |  28 ---
 .../core/persistence/jpa/outer/ManagerTest.java    | 270 +++++++++++++++++++++
 .../core/persistence/neo4j/dao/repo/GroupRepo.java |  46 ----
 .../persistence/neo4j/dao/repo/GroupRepoExt.java   |  26 +-
 .../neo4j/dao/repo/GroupRepoExtImpl.java           | 238 ++++++++++++------
 .../neo4j/dao/repo/UserRepoExtImpl.java            | 127 ++++++----
 .../core/persistence/neo4j/outer/GroupTest.java    |  27 ---
 .../core/persistence/neo4j/outer/ManagerTest.java  | 259 ++++++++++++++++++++
 .../syncope/core/starter/SelfKeymasterContext.java |   2 +-
 .../core/spring/security/AuthDataAccessor.java     |  55 +++--
 pom.xml                                            |  54 ++---
 17 files changed, 1180 insertions(+), 401 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 c92aa39317..19a6b8a0d5 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
@@ -38,9 +38,13 @@ public interface GroupDAO extends AnyDAO<Group> {
 
     Optional<? extends Group> findByName(String name);
 
+    List<String> findKeysByNamePattern(String pattern);
+
     @Override
     Collection<String> findAllResourceKeys(String key);
 
+    Map<String, Long> countByRealm();
+
     /**
      * Checks if the calling user is authorized to access the Group matching 
the provided key, under the given
      * realm.
@@ -51,33 +55,73 @@ public interface GroupDAO extends AnyDAO<Group> {
      */
     void securityChecks(Set<String> authRealms, String key, String realm);
 
-    boolean isManager(String key);
-
-    List<User> findManagedUsers(String key);
-
-    List<Group> findManagedGroups(String key);
-
-    List<AnyObject> findManagedAnyObjects(String key);
-
-    Map<String, Long> countByRealm();
+    long countUMembers(String groupKey);
 
-    List<String> findKeysByNamePattern(String pattern);
+    List<String> findUMembers(String groupKey);
 
-    List<AMembership> findAMemberships(Group group);
+    boolean existsUMembership(String userKey, String groupKey);
 
     List<UMembership> findUMemberships(Group group, Pageable pageable);
 
-    List<String> findAMembers(String groupKey);
+    long countAMembers(String groupKey);
 
-    List<String> findUMembers(String groupKey);
+    List<String> findAMembers(String groupKey);
 
     boolean existsAMembership(String anyObjectKey, String groupKey);
 
-    boolean existsUMembership(String userKey, String groupKey);
+    List<AMembership> findAMemberships(Group group);
 
-    long countAMembers(String groupKey);
+    List<GroupTypeExtension> findTypeExtensions(AnyTypeClass anyTypeClass);
 
-    long countUMembers(String groupKey);
+    boolean isManager(String key);
 
-    List<GroupTypeExtension> findTypeExtensions(AnyTypeClass anyTypeClass);
+    /**
+     * Returns all users managed by the group for the given key.
+     *
+     * Given:
+     *   * group G1 for the provided key
+     *   * group G2, with user member U
+     *
+     * then we have 2 cases where U is managed by G1:
+     *
+     * (a) U has gManager set to G1
+     * (b) G2 has gManager set to G1
+     *
+     * @param key manager key
+     * @return users managed by the group for the given key
+     */
+    List<User> findManagedUsers(String key);
+
+    /**
+     * Returns all groups managed by the group for the given key.
+     *
+     * Given:
+     *   * group G1 for the provided key
+     *   * group G2
+     *
+     * then we have 1 case where G2 is managed by G1:
+     *
+     * (a) G2 has gManager set to G1
+     *
+     * @param key manager key
+     * @return groups managed by the group for the given key
+     */
+    List<Group> findManagedGroups(String key);
+
+    /**
+     * Returns all any objects managed by the group for the given key.
+     *
+     * Given:
+     *   * group G1 for the provided key
+     *   * group G2, with any object member O
+     *
+     * then we have 2 cases where O is managed by G1:
+     *
+     * (a) O has gManager set to G1
+     * (b) G2 has gManager set to G1
+     *
+     * @param key manager key
+     * @return any objects managed by the group for the given key
+     */
+    List<AnyObject> findManagedAnyObjects(String key);
 }
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
index 620284e35f..e35f36b87a 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
@@ -51,10 +51,59 @@ public interface UserDAO extends AnyDAO<User> {
 
     boolean isManager(String key);
 
+    /**
+     * Returns all users managed by the user for the given key.
+     *
+     * Given:
+     *   * user U1 for the provided key, member of group G1
+     *   * user U2, member of group G2
+     *
+     * then we have 4 cases where U2 is managed by U1:
+     *
+     * (a) U2 has uManager set to U1
+     * (b) G2 has uManager set to U1
+     * (c) U2 has gManager set to G1
+     * (d) G2 has gManager set to G1
+     *
+     * @param key manager key
+     * @return users managed by the user for the given key
+     */
     List<User> findManagedUsers(String key);
 
+    /**
+     * Returns all groups managed by the user for the given key.
+     *
+     * Given:
+     *   * user U1 for the provided key, member of group G1
+     *   * group G2
+     *
+     * then we have 2 cases where G2 is managed by U1:
+     *
+     * (a) G2 has uManager set to U1
+     * (b) G2 has gManager set to G1
+     *
+     * @param key manager key
+     * @return groups managed by the user for the given key
+     */
     List<Group> findManagedGroups(String key);
 
+    /**
+     * Returns all any objects managed by the user for the given key.
+     *
+     * Given:
+     *   * user U for the provided key, member of group G1
+     *   * any object O, member of group G2
+     *
+     * then we have 4 cases where O is managed by U:
+     *
+     * (a) O has uManager set to U
+     * (b) G2 has uManager set to U
+     * (c) O has gManager set to G1
+     * (d) G2 has gManager set to G1
+     *
+     * @param key manager key
+     * @return any objects managed by the user for the given key
+     */
     List<AnyObject> findManagedAnyObjects(String key);
 
     Map<String, Long> countByRealm();
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepo.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepo.java
index d992d45aa0..a49692428e 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepo.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepo.java
@@ -41,20 +41,4 @@ public interface GroupRepo
     @Query("SELECT e FROM #{#entityName} e WHERE e.id IN (:keys)")
     @Override
     List<Group> findByKeys(@Param("keys") List<String> keys);
-
-    @Query("SELECT DISTINCT e.leftEnd.id FROM JPAAMembership e WHERE 
e.rightEnd.id = :groupKey")
-    @Override
-    List<String> findAMembers(@Param("groupKey") String groupKey);
-
-    @Query("SELECT DISTINCT e.leftEnd.id FROM JPAUMembership e WHERE 
e.rightEnd.id = :groupKey")
-    @Override
-    List<String> findUMembers(@Param("groupKey") String groupKey);
-
-    @Query("SELECT COUNT(DISTINCT e.leftEnd.id) FROM JPAAMembership e WHERE 
e.rightEnd.id = :groupKey")
-    @Override
-    long countAMembers(@Param("groupKey") String groupKey);
-
-    @Query("SELECT COUNT(DISTINCT e.leftEnd.id) FROM JPAUMembership e WHERE 
e.rightEnd.id = :groupKey")
-    @Override
-    long countUMembers(@Param("groupKey") String groupKey);
 }
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 2a4cd46d99..e161420c32 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
@@ -32,28 +32,36 @@ import org.springframework.data.domain.Pageable;
 
 public interface GroupRepoExt extends AnyRepoExt<Group> {
 
+    Map<String, Long> countByRealm();
+
     void securityChecks(Set<String> authRealms, String key, String realm);
 
-    boolean isManager(String key);
+    long countUMembers(String groupKey);
 
-    List<User> findManagedUsers(String key);
+    List<String> findUMembers(String groupKey);
 
-    List<Group> findManagedGroups(String key);
+    boolean existsUMembership(String userKey, String groupKey);
 
-    List<AnyObject> findManagedAnyObjects(String key);
+    List<UMembership> findUMemberships(Group group, Pageable pageable);
 
-    Map<String, Long> countByRealm();
+    long countAMembers(String groupKey);
 
-    boolean existsAMembership(String anyObjectKey, String groupKey);
+    List<String> findAMembers(String groupKey);
 
-    boolean existsUMembership(String userKey, String groupKey);
+    boolean existsAMembership(String anyObjectKey, String groupKey);
 
     List<AMembership> findAMemberships(Group group);
 
-    List<UMembership> findUMemberships(Group group, Pageable pageable);
-
     List<GroupTypeExtension> findTypeExtensions(AnyTypeClass anyTypeClass);
 
+    boolean isManager(String key);
+
+    List<User> findManagedUsers(String key);
+
+    List<Group> findManagedGroups(String key);
+
+    List<AnyObject> findManagedAnyObjects(String key);
+
     @Override
     <S extends Group> S save(S 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 ca74d5f9e8..329cfa264a 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
@@ -20,12 +20,15 @@ package org.apache.syncope.core.persistence.jpa.dao.repo;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.TypedQuery;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.AnyChecker;
@@ -86,6 +89,29 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group> implements Group
         this.anyObjectDAO = anyObjectDAO;
     }
 
+    @Transactional(readOnly = true)
+    @Override
+    public Collection<String> findAllResourceKeys(final String key) {
+        return findById(key).map(Any::getResources).
+                orElseGet(List::of).
+                stream().map(ExternalResource::getKey).toList();
+    }
+
+    @Override
+    public Map<String, Long> countByRealm() {
+        return query(
+                "SELECT r.fullPath, COUNT(e.id) "
+                + "FROM " + JPAGroup.TABLE + " e JOIN Realm r ON 
e.realm_id=r.id "
+                + "GROUP BY r.fullPath",
+                rs -> {
+                    Map<String, Long> result = new HashMap<>();
+                    while (rs.next()) {
+                        result.put(rs.getString(1), rs.getLong(2));
+                    }
+                    return result;
+                });
+    }
+
     @Transactional(readOnly = true)
     @Override
     public void securityChecks(
@@ -119,82 +145,81 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group> implements Group
 
     @Transactional(readOnly = true)
     @Override
-    public boolean isManager(final String key) {
-        long users = query(
-                "SELECT COUNT(*) FROM " + JPAUser.TABLE + " WHERE 
gManager_id=?",
+    public long countUMembers(final String groupKey) {
+        return query(
+                "SELECT COUNT(DISTINCT user_id) FROM " + JPAUMembership.TABLE 
+ " WHERE group_id=?",
                 rs -> {
                     rs.next();
                     return rs.getLong(1);
                 },
-                key);
+                groupKey);
+    }
 
-        long groups = query(
-                "SELECT COUNT(*) FROM " + JPAGroup.TABLE + " WHERE 
gManager_id=?",
+    @Override
+    public List<String> findUMembers(final String groupKey) {
+        return query(
+                "SELECT DISTINCT user_id FROM " + JPAUMembership.TABLE + " 
WHERE group_id=?",
                 rs -> {
-                    rs.next();
-                    return rs.getLong(1);
+                    List<String> result = new ArrayList<>();
+                    while (rs.next()) {
+                        result.add(rs.getString(1));
+                    }
+                    return result;
                 },
-                key);
+                groupKey);
+    }
 
-        long anyObjects = query(
-                "SELECT COUNT(*) FROM " + JPAAnyObject.TABLE + " WHERE 
gManager_id=?",
+    @Override
+    public boolean existsUMembership(final String userKey, final String 
groupKey) {
+        return query(
+                "SELECT COUNT(*) FROM " + JPAUMembership.TABLE + " WHERE 
group_id=? AND user_id=?",
                 rs -> {
                     rs.next();
                     return rs.getLong(1);
                 },
-                key);
-
-        return users + groups + anyObjects > 0;
+                groupKey, userKey) > 0;
     }
 
     @Override
-    public List<User> findManagedUsers(final String key) {
-        TypedQuery<User> query = entityManager.createQuery(
-                "SELECT e FROM " + JPAUser.class.getSimpleName() + " e WHERE 
e.gManager.id=:key", User.class);
-        query.setParameter("key", key);
-        return query.getResultList();
-    }
+    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 ORDER BY e.leftEnd",
+                UMembership.class);
+        query.setParameter("group", group);
+        if (pageable.isPaged()) {
+            query.setFirstResult(pageable.getPageSize() * 
pageable.getPageNumber());
+            query.setMaxResults(pageable.getPageSize());
+        }
 
-    @Override
-    public List<Group> findManagedGroups(final String key) {
-        TypedQuery<Group> query = entityManager.createQuery(
-                "SELECT e FROM " + JPAGroup.class.getSimpleName() + " e WHERE 
e.gManager.id=:key", Group.class);
-        query.setParameter("key", key);
         return query.getResultList();
     }
 
     @Override
-    public List<AnyObject> findManagedAnyObjects(final String key) {
-        TypedQuery<AnyObject> query = entityManager.createQuery(
-                "SELECT e FROM " + JPAAnyObject.class.getSimpleName() + " e 
WHERE e.gManager.id=:key", AnyObject.class);
-        query.setParameter("key", key);
-        return query.getResultList();
+    public long countAMembers(final String groupKey) {
+        return query(
+                "SELECT COUNT(DISTINCT anyObject_id) FROM " + 
JPAAMembership.TABLE + " WHERE group_id=?",
+                rs -> {
+                    rs.next();
+                    return rs.getLong(1);
+                },
+                groupKey);
     }
 
     @Override
-    public Map<String, Long> countByRealm() {
+    public List<String> findAMembers(final String groupKey) {
         return query(
-                "SELECT r.fullPath, COUNT(e.id) "
-                + "FROM " + JPAGroup.TABLE + " e JOIN Realm r ON 
e.realm_id=r.id "
-                + "GROUP BY r.fullPath",
+                "SELECT DISTINCT anyObject_id FROM " + JPAAMembership.TABLE + 
" WHERE group_id=?",
                 rs -> {
-                    Map<String, Long> result = new HashMap<>();
+                    List<String> result = new ArrayList<>();
                     while (rs.next()) {
-                        result.put(rs.getString(1), rs.getLong(2));
+                        result.add(rs.getString(1));
                     }
                     return result;
-                });
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public Collection<String> findAllResourceKeys(final String key) {
-        return findById(key).map(Any::getResources).
-                orElseGet(List::of).
-                stream().map(ExternalResource::getKey).toList();
+                },
+                groupKey);
     }
 
-    @Transactional(readOnly = true)
     @Override
     public boolean existsAMembership(final String anyObjectKey, final String 
groupKey) {
         return query(
@@ -206,41 +231,100 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group> implements Group
                 groupKey, anyObjectKey) > 0;
     }
 
+    @Override
+    public List<AMembership> findAMemberships(final Group group) {
+        TypedQuery<AMembership> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAAMembership.class.getSimpleName() + " e 
WHERE e.rightEnd=:group",
+                AMembership.class);
+        query.setParameter("group", group);
+
+        return query.getResultList();
+    }
+
     @Transactional(readOnly = true)
     @Override
-    public boolean existsUMembership(final String userKey, final String 
groupKey) {
-        return query(
-                "SELECT COUNT(*) FROM " + JPAUMembership.TABLE + " WHERE 
group_id=? AND user_id=?",
+    public boolean isManager(final String key) {
+        long users = query(
+                "SELECT COUNT(*) FROM " + JPAUser.TABLE + " WHERE 
gManager_id=?",
                 rs -> {
                     rs.next();
                     return rs.getLong(1);
                 },
-                groupKey, userKey) > 0;
+                key);
+
+        long groups = query(
+                "SELECT COUNT(*) FROM " + JPAGroup.TABLE + " WHERE 
gManager_id=?",
+                rs -> {
+                    rs.next();
+                    return rs.getLong(1);
+                },
+                key);
+
+        long anyObjects = query(
+                "SELECT COUNT(*) FROM " + JPAAnyObject.TABLE + " WHERE 
gManager_id=?",
+                rs -> {
+                    rs.next();
+                    return rs.getLong(1);
+                },
+                key);
+
+        return users + groups + anyObjects > 0;
     }
 
     @Override
-    public List<AMembership> findAMemberships(final Group group) {
-        TypedQuery<AMembership> query = entityManager.createQuery(
-                "SELECT e FROM " + JPAAMembership.class.getSimpleName() + " e 
WHERE e.rightEnd=:group",
-                AMembership.class);
-        query.setParameter("group", group);
+    public List<User> findManagedUsers(final String key) {
+        List<User> result = new ArrayList<>();
 
-        return query.getResultList();
+        // (a) see GroupDAO#findManagedUsers
+        TypedQuery<User> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAUser.class.getSimpleName() + " e WHERE 
e.gManager.id=:key", User.class);
+        query.setParameter("key", key);
+        result.addAll(query.getResultList());
+
+        // (b) see GroupDAO#findManagedUsers
+        findManagedGroupKeys(key).forEach(group -> findUMembers(group).
+                forEach(m -> userDAO.findById(m).ifPresent(result::add)));
+
+        return result.stream().distinct().toList();
+    }
+
+    protected Stream<String> findManagedGroupKeys(final String key) {
+        return query(
+                "SELECT DISTINCT id FROM " + JPAGroup.TABLE + " WHERE 
gManager_id=?",
+                rs -> {
+                    List<String> result = new ArrayList<>();
+                    while (rs.next()) {
+                        result.add(rs.getString(1));
+                    }
+                    return result.stream();
+                },
+                key);
     }
 
     @Override
-    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 ORDER BY e.leftEnd",
-                UMembership.class);
-        query.setParameter("group", group);
-        if (pageable.isPaged()) {
-            query.setFirstResult(pageable.getPageSize() * 
pageable.getPageNumber());
-            query.setMaxResults(pageable.getPageSize());
-        }
+    public List<Group> findManagedGroups(final String key) {
+        return findManagedGroupKeys(key).
+                map(group -> 
Optional.ofNullable(entityManager.find(JPAGroup.class, group))).
+                flatMap(Optional::stream).
+                map(Group.class::cast).
+                toList();
+    }
 
-        return query.getResultList();
+    @Override
+    public List<AnyObject> findManagedAnyObjects(final String key) {
+        List<AnyObject> result = new ArrayList<>();
+
+        // (a) see GroupDAO#findManagedAnyObjects
+        TypedQuery<AnyObject> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAAnyObject.class.getSimpleName() + " e 
WHERE e.gManager.id=:key", AnyObject.class);
+        query.setParameter("key", key);
+        result.addAll(query.getResultList());
+
+        // (b) see GroupDAO#findManagedUsers
+        findManagedGroupKeys(key).forEach(group -> findAMembers(group).
+                forEach(m -> anyObjectDAO.findById(m).ifPresent(result::add)));
+
+        return result.stream().distinct().toList();
     }
 
     @Override
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
index 8cff853441..8fa4c6f927 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
@@ -31,6 +31,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
@@ -47,6 +48,7 @@ import 
org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.api.utils.RealmUtils;
+import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
@@ -138,72 +140,82 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User> implements UserRep
         return users + groups + anyObjects > 0;
     }
 
+    protected Stream<String> findUMembershipGroups(final String key) {
+        Query query = entityManager.createNativeQuery(
+                "SELECT DISTINCT group_id FROM " + JPAUMembership.TABLE + " 
WHERE user_id=?");
+        query.setParameter(1, key);
+
+        @SuppressWarnings("unchecked")
+        List<Object> groups = query.getResultList();
+        return groups.stream().map(String.class::cast);
+    }
+
     @Override
     public List<User> findManagedUsers(final String key) {
         List<User> result = new ArrayList<>();
 
-        // 1. users having uManager set to the requested user
+        // (a) see UserDAO#findManagedUsers
         TypedQuery<User> users = entityManager.createQuery(
                 "SELECT e FROM " + JPAUser.class.getSimpleName() + " e WHERE 
e.uManager.id=:key", User.class);
         users.setParameter("key", key);
         result.addAll(users.getResultList());
 
-        // 2. user members of groups having uManager set to the requested user
+        // (b) see UserDAO#findManagedUsers
         findManagedGroupKeys(key).forEach(group -> 
groupDAO.findUMembers(group).
-                forEach(member -> 
Optional.ofNullable(entityManager.find(JPAUser.class, member)).
-                ifPresent(result::add)));
+                forEach(m -> 
Optional.ofNullable(entityManager.find(JPAUser.class, 
m)).ifPresent(result::add)));
 
-        // 3. users managed by groups the requested user is member of
-        Query query = entityManager.createNativeQuery(
-                "SELECT DISTINCT group_id FROM " + JPAUMembership.TABLE + " 
WHERE user_id=?");
-        query.setParameter(1, key);
-        @SuppressWarnings("unchecked")
-        List<Object> groups = query.getResultList();
-        groups.stream().map(String.class::cast).forEach(group -> 
result.addAll(groupDAO.findManagedUsers(group)));
+        // (c) see UserDAO#findManagedUsers
+        // (d) see UserDAO#findManagedUsers
+        findUMembershipGroups(key).forEach(group -> 
result.addAll(groupDAO.findManagedUsers(group)));
 
         return result.stream().distinct().toList();
     }
 
-    protected List<String> findManagedGroupKeys(final String key) {
+    protected Stream<String> findManagedGroupKeys(final String key) {
         Query query = entityManager.createNativeQuery(
                 "SELECT DISTINCT id FROM " + JPAGroup.TABLE + " WHERE 
uManager_id=?");
         query.setParameter(1, key);
 
         @SuppressWarnings("unchecked")
         List<Object> result = query.getResultList();
-        return result.stream().map(String.class::cast).toList();
+        return result.stream().map(String.class::cast);
     }
 
     @Override
     public List<Group> findManagedGroups(final String key) {
-        return findManagedGroupKeys(key).stream().
-                map(group -> 
Optional.ofNullable(entityManager.find(JPAGroup.class, group))).
-                flatMap(Optional::stream).
-                map(Group.class::cast).
-                toList();
+        List<Group> result = new ArrayList<>();
+
+        // (a) see UserDAO#findManagedGroups
+        findManagedGroupKeys(key).forEach(group -> 
groupDAO.findById(group).ifPresent(result::add));
+
+        // (b) see UserDAO#findManagedGroups
+        findUMembershipGroups(key).forEach(g -> 
result.addAll(groupDAO.findManagedGroups(g)));
+
+        return result.stream().distinct().toList();
     }
 
     @Override
     public List<AnyObject> findManagedAnyObjects(final String key) {
         List<AnyObject> result = new ArrayList<>();
 
-        // 1. anyObjects having uManager set to the requested user
+        // (a) see UserDAO#findManagedAnyObjects
         TypedQuery<AnyObject> anyObjects = entityManager.createQuery(
                 "SELECT e FROM " + JPAAnyObject.class.getSimpleName() + " e 
WHERE e.uManager.id=:key", AnyObject.class);
         anyObjects.setParameter("key", key);
         result.addAll(anyObjects.getResultList());
 
-        // 2. anyObject members of groups having uManager set to the requested 
user
-        findManagedGroupKeys(key).forEach(group -> 
groupDAO.findUMembers(group).
-                forEach(member -> 
Optional.ofNullable(entityManager.find(JPAAnyObject.class, member)).
-                ifPresent(result::add)));
+        // (b) see UserDAO#findManagedAnyObjects
+        findManagedGroupKeys(key).forEach(group -> 
groupDAO.findAMembers(group).
+                forEach(m -> 
Optional.ofNullable(entityManager.find(JPAAnyObject.class, 
m)).ifPresent(result::add)));
 
-        // 3. anyObject managed by groups the requested user is member of
         Query query = entityManager.createNativeQuery(
-                "SELECT DISTINCT group_id FROM " + JPAUMembership.TABLE + " 
WHERE user_id=?");
+                "SELECT DISTINCT group_id FROM " + JPAAMembership.TABLE + " 
WHERE anyObject_id=?");
         query.setParameter(1, key);
         @SuppressWarnings("unchecked")
         List<Object> groups = query.getResultList();
+
+        // (c) see UserDAO#findManagedAnyObjects
+        // (d) see UserDAO#findManagedAnyObjects
         groups.stream().map(String.class::cast).forEach(group -> 
result.addAll(groupDAO.findManagedAnyObjects(group)));
 
         return result.stream().distinct().toList();
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 d7129e1ac2..306ed2b0c2 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
@@ -143,34 +143,6 @@ public class GroupTest extends AbstractTest {
         });
     }
 
-    @Test
-    public void findByUManager() {
-        Group group = 
groupDAO.findById("ebf97068-aa4b-4a85-9f01-680e8c4cf227").orElseThrow();
-
-        User user = 
userDAO.findById("823074dc-d280-436d-a7dd-07399fae48ec").orElseThrow();
-
-        assertEquals(user, group.getuManager());
-
-        List<Group> managed = userDAO.findManagedGroups(user.getKey());
-        assertFalse(managed.isEmpty());
-        assertEquals(1, managed.size());
-        assertTrue(managed.contains(group));
-    }
-
-    @Test
-    public void findByGManager() {
-        Group root = groupDAO.findByName("root").orElseThrow();
-        Group group = entityFactory.newEntity(Group.class);
-        group.setRealm(realmDAO.getRoot());
-        group.setName("error");
-        group.setgManager(root);
-        group = groupDAO.save(group);
-        entityManager.flush();
-
-        List<Group> managed = groupDAO.findManagedGroups(root.getKey());
-        assertEquals(List.of(group), managed);
-    }
-
     @Test
     public void create() {
         Group group = entityFactory.newEntity(Group.class);
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ManagerTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ManagerTest.java
new file mode 100644
index 0000000000..f1a856bfda
--- /dev/null
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ManagerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.outer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.transaction.annotation.Transactional;
+
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
+@Transactional
+class ManagerTest extends AbstractTest {
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private GroupDAO groupDAO;
+
+    @Autowired
+    private RealmDAO realmDAO;
+
+    /**
+     * (a) U2 has uManager set to U1
+     */
+    @Test
+    void findUManagedUsersA() {
+        User vivaldi = userDAO.findByUsername("vivaldi").orElseThrow();
+        assertNull(vivaldi.getgManager());
+        assertNull(vivaldi.getuManager());
+
+        User rossini = userDAO.findByUsername("rossini").orElseThrow();
+        assertFalse(userDAO.isManager(rossini.getKey()));
+
+        vivaldi.setuManager(rossini);
+        vivaldi = userDAO.save(vivaldi);
+        entityManager.flush();
+        assertEquals(rossini, vivaldi.getuManager());
+        assertTrue(userDAO.isManager(rossini.getKey()));
+
+        List<User> managed = userDAO.findManagedUsers(rossini.getKey());
+        assertEquals(List.of(vivaldi), managed);
+    }
+
+    /**
+     * (b) G2 has uManager set to U1
+     */
+    @Test
+    void findUManagedUsersB() {
+        Group artDirector = groupDAO.findByName("artDirector").orElseThrow();
+        assertNull(artDirector.getuManager());
+        assertNull(artDirector.getgManager());
+
+        User vivaldi = userDAO.findByUsername("vivaldi").orElseThrow();
+        assertFalse(userDAO.isManager(vivaldi.getKey()));
+
+        artDirector.setuManager(vivaldi);
+        artDirector = groupDAO.save(artDirector);
+        entityManager.flush();
+        assertEquals(vivaldi, artDirector.getuManager());
+        assertTrue(userDAO.isManager(vivaldi.getKey()));
+
+        User puccini = userDAO.findByUsername("puccini").orElseThrow();
+        assertNull(puccini.getgManager());
+        assertNull(puccini.getuManager());
+        assertTrue(puccini.getMembership(artDirector.getKey()).isPresent());
+
+        List<User> managed = userDAO.findManagedUsers(vivaldi.getKey());
+        assertEquals(List.of(puccini), managed);
+    }
+
+    /**
+     * (c) U2 has gManager set to G1
+     */
+    @Test
+    void findUManagedUsersC() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+        assertFalse(userDAO.isManager(bellini.getKey()));
+
+        User rossini = userDAO.findByUsername("rossini").orElseThrow();
+        assertNull(rossini.getgManager());
+        assertNull(rossini.getuManager());
+
+        rossini.setgManager(managingDirector);
+        rossini = userDAO.save(rossini);
+        entityManager.flush();
+        assertEquals(managingDirector, rossini.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        List<User> managed = userDAO.findManagedUsers(bellini.getKey());
+        assertEquals(List.of(rossini), managed);
+    }
+
+    /**
+     * (d) G2 has gManager set to G1
+     */
+    @Test
+    void findUManagedUsersD() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+        assertFalse(userDAO.isManager(bellini.getKey()));
+
+        Group otherchild = groupDAO.findByName("otherchild").orElseThrow();
+        assertNull(otherchild.getgManager());
+        assertNull(otherchild.getuManager());
+
+        User rossini = userDAO.findByUsername("rossini").orElseThrow();
+        assertTrue(rossini.getMembership(otherchild.getKey()).isPresent());
+        rossini.setuManager(null);
+        rossini.setgManager(null);
+        rossini = userDAO.save(rossini);
+        entityManager.flush();
+        assertNull(rossini.getgManager());
+        assertNull(rossini.getuManager());
+
+        otherchild.setgManager(managingDirector);
+        otherchild = groupDAO.save(otherchild);
+        entityManager.flush();
+        assertEquals(managingDirector, otherchild.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        List<User> managed = userDAO.findManagedUsers(bellini.getKey());
+        assertEquals(List.of(rossini), managed);
+    }
+
+    /**
+     * (a) G2 has uManager set to U1 - see UserDAO#findManagedGroups
+     */
+    @Test
+    void findUManagedGroupsA() {
+        Group director = groupDAO.findByName("director").orElseThrow();
+        User puccini = userDAO.findByUsername("puccini").orElseThrow();
+        assertEquals(puccini, director.getuManager());
+        assertTrue(userDAO.isManager(puccini.getKey()));
+
+        List<Group> managed = userDAO.findManagedGroups(puccini.getKey());
+        assertEquals(List.of(director), managed);
+    }
+
+    /**
+     * (b) G2 has gManager set to G1 - see UserDAO#findManagedGroups
+     */
+    @Test
+    void findUManagedGroupsB() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        Group root = groupDAO.findByName("root").orElseThrow();
+        assertNull(root.getgManager());
+        assertNull(root.getuManager());
+
+        root.setgManager(managingDirector);
+        root = groupDAO.save(root);
+        entityManager.flush();
+        assertEquals(managingDirector, root.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+
+        List<Group> managed = userDAO.findManagedGroups(bellini.getKey());
+        assertEquals(List.of(root), managed);
+    }
+
+    /**
+     * (a) U has gManager set to G1
+     */
+    @Test
+    void findGManagedUsersA() {
+        Group citizen = groupDAO.findByName("citizen").orElseThrow();
+        assertFalse(groupDAO.isManager(citizen.getKey()));
+
+        User verdi = userDAO.findByUsername("verdi").orElseThrow();
+        assertNull(verdi.getgManager());
+        assertNull(verdi.getuManager());
+
+        verdi.setgManager(citizen);
+        verdi = userDAO.save(verdi);
+        entityManager.flush();
+        assertEquals(citizen, verdi.getgManager());
+        assertTrue(groupDAO.isManager(citizen.getKey()));
+
+        List<User> managed = groupDAO.findManagedUsers(citizen.getKey());
+        assertEquals(List.of(verdi), managed);
+    }
+
+    /**
+     * (b) G2 has gManager set to G1
+     */
+    @Test
+    void findGManagedUsersB() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+
+        Group citizen = groupDAO.findByName("citizen").orElseThrow();
+        assertNull(citizen.getgManager());
+        assertNull(citizen.getuManager());
+
+        User verdi = userDAO.findByUsername("verdi").orElseThrow();
+        assertTrue(verdi.getMembership(citizen.getKey()).isPresent());
+
+        citizen.setgManager(managingDirector);
+        citizen = groupDAO.save(citizen);
+        entityManager.flush();
+        assertEquals(managingDirector, citizen.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        List<User> managed = 
groupDAO.findManagedUsers(managingDirector.getKey());
+        assertEquals(List.of(verdi), managed);
+    }
+
+    /**
+     * (a) G2 has gManager set to G1 - see GroupDAO#findManagedGroups
+     */
+    @Test
+    void findGManagedGroups() {
+        Group root = groupDAO.findByName("root").orElseThrow();
+        assertFalse(groupDAO.isManager(root.getKey()));
+
+        Group group = entityFactory.newEntity(Group.class);
+        group.setRealm(realmDAO.getRoot());
+        group.setName("error");
+        group.setgManager(root);
+        group = groupDAO.save(group);
+        entityManager.flush();
+        assertEquals(root, group.getgManager());
+        assertTrue(groupDAO.isManager(root.getKey()));
+
+        List<Group> managed = groupDAO.findManagedGroups(root.getKey());
+        assertEquals(List.of(group), managed);
+    }
+}
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepo.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepo.java
index c6351fb358..4bcabe8dc1 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepo.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepo.java
@@ -21,11 +21,7 @@ package org.apache.syncope.core.persistence.neo4j.dao.repo;
 import java.util.List;
 import java.util.Optional;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
-import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAMembership;
-import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject;
 import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroup;
-import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUMembership;
-import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUser;
 import org.springframework.data.neo4j.repository.query.Query;
 import org.springframework.data.repository.PagingAndSortingRepository;
 import org.springframework.data.repository.query.Param;
@@ -40,46 +36,4 @@ public interface GroupRepo
     @Query("MATCH (n:" + Neo4jGroup.NODE + ") WHERE toLower(n.name) =~ 
$pattern RETURN n.id")
     @Override
     List<String> findKeysByNamePattern(@Param("pattern") String pattern);
-
-    @Query("MATCH (a:" + Neo4jAnyObject.NODE + " {id: $anyObjectKey})-[]-"
-            + "(n:" + Neo4jAMembership.NODE + ")-[]-"
-            + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
-            + "RETURN COUNT(n) > 0")
-    @Override
-    boolean existsAMembership(String anyObjectKey, String groupKey);
-
-    @Query("MATCH (u:" + Neo4jUser.NODE + " {id: $userKey})-[]-"
-            + "(n:" + Neo4jUMembership.NODE + ")-[]-"
-            + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
-            + "RETURN COUNT(n) > 0")
-    @Override
-    boolean existsUMembership(String userKey, String groupKey);
-
-    @Query("MATCH (a:" + Neo4jAnyObject.NODE + ")-[]-"
-            + "(n:" + Neo4jAMembership.NODE + ")-[]-"
-            + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
-            + "RETURN a.id")
-    @Override
-    List<String> findAMembers(@Param("groupKey") String groupKey);
-
-    @Query("MATCH (u:" + Neo4jUser.NODE + ")-[]-"
-            + "(n:" + Neo4jUMembership.NODE + ")-[]-"
-            + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
-            + "RETURN u.id")
-    @Override
-    List<String> findUMembers(@Param("groupKey") String groupKey);
-
-    @Query("MATCH (a:" + Neo4jAnyObject.NODE + ")-[]-"
-            + "(n:" + Neo4jAMembership.NODE + ")-[]-"
-            + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
-            + "RETURN COUNT(DISTINCT a)")
-    @Override
-    long countAMembers(@Param("groupKey") String groupKey);
-
-    @Query("MATCH (u:" + Neo4jUser.NODE + ")-[]-"
-            + "(n:" + Neo4jUMembership.NODE + ")-[]-"
-            + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
-            + "RETURN COUNT(DISTINCT u)")
-    @Override
-    long countUMembers(@Param("groupKey") String groupKey);
 }
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 3adafe726f..7e3cfeae95 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
@@ -34,24 +34,36 @@ public interface GroupRepoExt extends AnyRepoExt<Group> {
 
     String CACHE = "groupCache";
 
+    Map<String, Long> countByRealm();
+
     void securityChecks(Set<String> authRealms, String key, String realm);
 
-    boolean isManager(String key);
+    long countUMembers(String groupKey);
 
-    List<User> findManagedUsers(String key);
+    List<String> findUMembers(String groupKey);
 
-    List<Group> findManagedGroups(String key);
+    boolean existsUMembership(String userKey, String groupKey);
 
-    List<AnyObject> findManagedAnyObjects(String key);
+    List<UMembership> findUMemberships(Group group, Pageable pageable);
 
-    Map<String, Long> countByRealm();
+    long countAMembers(String groupKey);
 
-    List<AMembership> findAMemberships(Group group);
+    List<String> findAMembers(String groupKey);
 
-    List<UMembership> findUMemberships(Group group, Pageable pageable);
+    boolean existsAMembership(String anyObjectKey, String groupKey);
+
+    List<AMembership> findAMemberships(Group group);
 
     List<GroupTypeExtension> findTypeExtensions(AnyTypeClass anyTypeClass);
 
+    boolean isManager(String key);
+
+    List<User> findManagedUsers(String key);
+
+    List<Group> findManagedGroups(String key);
+
+    List<AnyObject> findManagedAnyObjects(String key);
+
     @Override
     <S extends Group> S save(S 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 5a06fd0624..482cdeb8a8 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
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.neo4j.dao.repo;
 
 import java.time.OffsetDateTime;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -26,6 +27,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import javax.cache.Cache;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
@@ -128,12 +130,27 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
         return groupCache;
     }
 
-    @Transactional(readOnly = true)
     @Override
     public Optional<OffsetDateTime> findLastChange(final String key) {
         return findLastChange(key, Neo4jGroup.NODE);
     }
 
+    @Override
+    public Collection<String> findAllResourceKeys(final String key) {
+        return findById(key).map(Any::getResources).
+                orElseGet(List::of).
+                stream().map(ExternalResource::getKey).toList();
+    }
+
+    @Override
+    public Map<String, Long> countByRealm() {
+        Collection<Map<String, Object>> result = neo4jClient.query(
+                "MATCH (n:" + Neo4jGroup.NODE + ")-[]-(r:" + Neo4jRealm.NODE + 
") "
+                + "RETURN r.fullPath AS realm, COUNT(n) AS 
counted").fetch().all();
+
+        return result.stream().collect(Collectors.toMap(r -> 
r.get("realm").toString(), r -> (Long) r.get("counted")));
+    }
+
     @Transactional(readOnly = true)
     @Override
     public void securityChecks(
@@ -158,6 +175,94 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
         }
     }
 
+    @Override
+    public long countUMembers(final String groupKey) {
+        return neo4jTemplate.count(
+                "MATCH (u:" + Neo4jUser.NODE + ")-[]-"
+                + "(n:" + Neo4jUMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
+                + "RETURN COUNT(DISTINCT u)",
+                Map.of("groupKey", groupKey));
+    }
+
+    @Override
+    public List<String> findUMembers(final String groupKey) {
+        Collection<Map<String, Object>> result = neo4jClient.query(
+                "MATCH (u:" + Neo4jUser.NODE + ")-[]-"
+                + "(n:" + Neo4jUMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
+                + "RETURN u.id").bindAll(Map.of("groupKey", 
groupKey)).fetch().all();
+        return result.stream().map(found -> 
found.get("u.id").toString()).toList();
+    }
+
+    @Override
+    public boolean existsUMembership(final String userKey, final String 
groupKey) {
+        return neo4jTemplate.count(
+                "MATCH (u:" + Neo4jUser.NODE + " {id: $userKey})-[]-"
+                + "(n:" + Neo4jUMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
+                + "RETURN COUNT(n)",
+                Map.of("userKey", userKey, "groupKey", groupKey)) > 0;
+    }
+
+    @Override
+    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" + paged)
+                        .bindAll(Map.of("id", group.getKey())).fetch().all(),
+                "n.id",
+                Neo4jUMembership.class,
+                null);
+    }
+
+    @Override
+    public long countAMembers(final String groupKey) {
+        return neo4jTemplate.count(
+                "MATCH (a:" + Neo4jAnyObject.NODE + ")-[]-"
+                + "(n:" + Neo4jAMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
+                + "RETURN COUNT(DISTINCT a)",
+                Map.of("groupKey", groupKey));
+    }
+
+    @Override
+    public List<String> findAMembers(final String groupKey) {
+        Collection<Map<String, Object>> result = neo4jClient.query(
+                "MATCH (a:" + Neo4jAnyObject.NODE + ")-[]-"
+                + "(n:" + Neo4jAMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
+                + "RETURN a.id").bindAll(Map.of("groupKey", 
groupKey)).fetch().all();
+        return result.stream().map(found -> 
found.get("a.id").toString()).toList();
+    }
+
+    @Override
+    public boolean existsAMembership(final String anyObjectKey, final String 
groupKey) {
+        return neo4jTemplate.count(
+                "MATCH (a:" + Neo4jAnyObject.NODE + " {id: $anyObjectKey})-[]-"
+                + "(n:" + Neo4jAMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + " {id: $groupKey}) "
+                + "RETURN COUNT(n)",
+                Map.of("anyObjectKey", anyObjectKey, "groupKey", groupKey)) > 
0;
+    }
+
+    @Override
+    public List<AMembership> findAMemberships(final Group group) {
+        return toList(
+                neo4jClient.query(
+                        "MATCH (n:" + Neo4jAMembership.NODE + ")-[]-(g:" + 
Neo4jGroup.NODE + " {id: $id}) "
+                        + "RETURN n.id").bindAll(Map.of("id", 
group.getKey())).fetch().all(),
+                "n.id",
+                Neo4jAMembership.class,
+                null);
+    }
+
     @Override
     protected void securityChecks(final Group group) {
         Set<String> authRealms = AuthContextUtils.getAuthorizations().
@@ -168,90 +273,87 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
 
     @Override
     public boolean isManager(final String key) {
-        return !findManagedUsers(key).isEmpty()
-                || !findManagedGroups(key).isEmpty()
-                || !findManagedAnyObjects(key).isEmpty();
+        long users = neo4jTemplate.count(
+                "MATCH (n:" + Neo4jGroup.NODE + " {id: $key})-"
+                + "[:" + AbstractAny.GROUP_MANAGER_REL + "]-"
+                + "(p:" + Neo4jUser.NODE + ") "
+                + "RETURN COUNT(p.id)",
+                Map.of("key", key));
+
+        long groups = neo4jTemplate.count(
+                "MATCH (n:" + Neo4jGroup.NODE + " {id: $key})-"
+                + "[:" + AbstractAny.GROUP_MANAGER_REL + "]-"
+                + "(p:" + Neo4jGroup.NODE + ") "
+                + "RETURN COUNT(p.id)",
+                Map.of("key", key));
+
+        long anyObjects = neo4jTemplate.count(
+                "MATCH (n:" + Neo4jGroup.NODE + " {id: $key})-"
+                + "[:" + AbstractAny.GROUP_MANAGER_REL + "]-"
+                + "(p:" + Neo4jAnyObject.NODE + ") "
+                + "RETURN COUNT(p.id)",
+                Map.of("key", key));
+
+        return users + groups + anyObjects > 0;
     }
 
     @Override
     public List<User> findManagedUsers(final String key) {
-        return toList(neo4jClient.query(
+        List<User> result = new ArrayList<>();
+
+        // (a) see GroupDAO#findManagedUsers
+        result.addAll(toList(neo4jClient.query(
                 "MATCH (n:" + Neo4jGroup.NODE + " {id: $id})-"
                 + "[:" + AbstractAny.GROUP_MANAGER_REL + "]-"
                 + "(p:" + Neo4jUser.NODE + ") "
                 + "RETURN p.id").bindAll(Map.of("id", key)).fetch().all(),
                 "p.id",
                 Neo4jUser.class,
-                userCache);
+                userCache));
+
+        // (b) see GroupDAO#findManagedUsers
+        findManagedGroupKeys(key).forEach(group -> findUMembers(group).
+                forEach(m -> userDAO.findById(m).ifPresent(result::add)));
+
+        return result.stream().distinct().toList();
     }
 
-    @Override
-    public List<Group> findManagedGroups(final String key) {
-        return toList(neo4jClient.query(
+    protected Stream<String> findManagedGroupKeys(final String key) {
+        Collection<Map<String, Object>> result = neo4jClient.query(
                 "MATCH (n:" + Neo4jGroup.NODE + " {id: $id})-"
                 + "[:" + AbstractAny.GROUP_MANAGER_REL + "]-"
                 + "(p:" + Neo4jGroup.NODE + ") "
-                + "RETURN p.id").bindAll(Map.of("id", key)).fetch().all(),
-                "p.id",
-                Neo4jGroup.class,
-                groupCache);
+                + "RETURN p.id").bindAll(Map.of("id", key)).fetch().all();
+        return result.stream().map(found -> found.get("p.id").toString());
+    }
+
+    @Override
+    public List<Group> findManagedGroups(final String key) {
+        return findManagedGroupKeys(key).map(group -> findById(group, 
Neo4jGroup.class, groupCache)).
+                flatMap(Optional::stream).
+                map(Group.class::cast).
+                toList();
     }
 
     @Override
     public List<AnyObject> findManagedAnyObjects(final String key) {
-        return toList(neo4jClient.query(
+        List<AnyObject> result = new ArrayList<>();
+
+        // (a) see GroupDAO#findManagedAnyObjects
+        result.addAll(toList(neo4jClient.query(
                 "MATCH (n:" + Neo4jGroup.NODE + " {id: $id})-"
                 + "[:" + AbstractAny.GROUP_MANAGER_REL + "]-"
                 + "(p:" + Neo4jGroup.NODE + ") "
                 + "RETURN p.id").bindAll(Map.of("id", key)).fetch().all(),
                 "p.id",
                 Neo4jAnyObject.class,
-                anyObjectCache);
-    }
-
-    @Override
-    public Map<String, Long> countByRealm() {
-        Collection<Map<String, Object>> result = neo4jClient.query(
-                "MATCH (n:" + Neo4jGroup.NODE + ")-[]-(r:" + Neo4jRealm.NODE + 
") "
-                + "RETURN r.fullPath AS realm, COUNT(n) AS 
counted").fetch().all();
-
-        return result.stream().collect(Collectors.toMap(r -> 
r.get("realm").toString(), r -> (Long) r.get("counted")));
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public Collection<String> findAllResourceKeys(final String key) {
-        return findById(key).map(Any::getResources).
-                orElseGet(List::of).
-                stream().map(ExternalResource::getKey).toList();
-    }
+                anyObjectCache));
 
-    @Override
-    public List<AMembership> findAMemberships(final Group group) {
-        return toList(
-                neo4jClient.query(
-                        "MATCH (n:" + Neo4jAMembership.NODE + ")-[]-(g:" + 
Neo4jGroup.NODE + " {id: $id}) "
-                        + "RETURN n.id").bindAll(Map.of("id", 
group.getKey())).fetch().all(),
-                "n.id",
-                Neo4jAMembership.class,
-                null);
-    }
+        // (b) see GroupDAO#findManagedUsers
+        findManagedGroupKeys(key).forEach(group -> findAMembers(group).
+                forEach(m -> anyObjectDAO.findById(m).ifPresent(result::add)));
 
-    @Override
-    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" + paged)
-                        .bindAll(Map.of("id", group.getKey())).fetch().all(),
-                "n.id",
-                Neo4jUMembership.class,
-                null);
+        return result.stream().distinct().toList();
     }
 
     @Override
@@ -263,18 +365,18 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
         neo4jTemplate.findById(group.getKey(), 
Neo4jGroup.class).ifPresent(before -> {
             before.getResources().stream().filter(resource -> 
!group.getResources().contains(resource)).
                     forEach(resource -> deleteRelationship(
-                    Neo4jGroup.NODE,
-                    Neo4jExternalResource.NODE,
-                    group.getKey(),
-                    resource.getKey(),
-                    Neo4jGroup.GROUP_RESOURCE_REL));
+                            Neo4jGroup.NODE,
+                            Neo4jExternalResource.NODE,
+                            group.getKey(),
+                            resource.getKey(),
+                            Neo4jGroup.GROUP_RESOURCE_REL));
             before.getAuxClasses().stream().filter(auxClass -> 
!group.getAuxClasses().contains(auxClass)).
                     forEach(auxClass -> deleteRelationship(
-                    Neo4jGroup.NODE,
-                    Neo4jAnyTypeClass.NODE,
-                    group.getKey(),
-                    auxClass.getKey(),
-                    Neo4jGroup.GROUP_AUX_CLASSES_REL));
+                            Neo4jGroup.NODE,
+                            Neo4jAnyTypeClass.NODE,
+                            group.getKey(),
+                            auxClass.getKey(),
+                            Neo4jGroup.GROUP_AUX_CLASSES_REL));
             if (before.getuManager() != null && group.getuManager() == null) {
                 deleteRelationship(
                         Neo4jGroup.NODE,
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
index b5759830f0..ab02096ff8 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
@@ -28,6 +28,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import javax.cache.Cache;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
@@ -57,6 +58,7 @@ import 
org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass;
 import org.apache.syncope.core.persistence.neo4j.entity.Neo4jExternalResource;
 import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRealm;
 import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRole;
+import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAMembership;
 import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject;
 import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroup;
 import 
org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jLinkedAccount;
@@ -166,16 +168,44 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User, Neo4jUser> impleme
 
     @Override
     public boolean isManager(final String key) {
-        return !findManagedUsers(key).isEmpty()
-                || !findManagedGroups(key).isEmpty()
-                || !findManagedAnyObjects(key).isEmpty();
+        long users = neo4jTemplate.count(
+                "MATCH (n:" + Neo4jUser.NODE + " {id: $key})-"
+                + "[:" + AbstractAny.USER_MANAGER_REL + "]-"
+                + "(p:" + Neo4jUser.NODE + ") "
+                + "RETURN COUNT(p.id)",
+                Map.of("key", key));
+
+        long groups = neo4jTemplate.count(
+                "MATCH (n:" + Neo4jUser.NODE + " {id: $key})-"
+                + "[:" + AbstractAny.USER_MANAGER_REL + "]-"
+                + "(p:" + Neo4jGroup.NODE + ") "
+                + "RETURN COUNT(p.id)",
+                Map.of("key", key));
+
+        long anyObjects = neo4jTemplate.count(
+                "MATCH (n:" + Neo4jUser.NODE + " {id: $key})-"
+                + "[:" + AbstractAny.USER_MANAGER_REL + "]-"
+                + "(p:" + Neo4jAnyObject.NODE + ") "
+                + "RETURN COUNT(p.id)",
+                Map.of("key", key));
+
+        return users + groups + anyObjects > 0;
+    }
+
+    protected Stream<String> findUMembershipGroups(final String key) {
+        Collection<Map<String, Object>> result = neo4jClient.query(
+                "MATCH (u:" + Neo4jUser.NODE + " {id: $key})-[]-"
+                + "(n:" + Neo4jUMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + ") "
+                + "RETURN g.id").bindAll(Map.of("key", key)).fetch().all();
+        return result.stream().map(found -> found.get("g.id").toString());
     }
 
     @Override
     public List<User> findManagedUsers(final String key) {
         List<User> result = new ArrayList<>();
 
-        // 1. users having uManager set to the requested user
+        // (a) see UserDAO#findManagedUsers
         result.addAll(toList(neo4jClient.query(
                 "MATCH (n:" + Neo4jUser.NODE + " {id: $id})-"
                 + "[:" + AbstractAny.USER_MANAGER_REL + "]-"
@@ -185,35 +215,44 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User, Neo4jUser> impleme
                 Neo4jUser.class,
                 userCache));
 
-        // 2. user members of groups having uManager set to the requested user
-        findManagedGroups(key).forEach(group -> 
groupDAO.findUMembers(group.getKey()).
-                forEach(member -> findById(member).
-                ifPresent(result::add)));
+        // (b) see UserDAO#findManagedUsers
+        findManagedGroupKeys(key).forEach(group -> 
groupDAO.findUMembers(group).
+                forEach(m -> findById(m).ifPresent(result::add)));
 
-        // 3. users managed by groups the requested user is member of
-        findById(key).ifPresent(user -> user.getMemberships().
-                forEach(m -> 
result.addAll(groupDAO.findManagedUsers(m.getRightEnd().getKey()))));
+        // (c) see UserDAO#findManagedUsers
+        // (d) see UserDAO#findManagedUsers
+        findUMembershipGroups(key).forEach(group -> 
result.addAll(groupDAO.findManagedUsers(group)));
 
         return result.stream().distinct().toList();
     }
 
-    @Override
-    public List<Group> findManagedGroups(final String key) {
-        return toList(neo4jClient.query(
+    protected Stream<String> findManagedGroupKeys(final String key) {
+        Collection<Map<String, Object>> result = neo4jClient.query(
                 "MATCH (n:" + Neo4jUser.NODE + " {id: $id})-"
                 + "[:" + AbstractAny.USER_MANAGER_REL + "]-"
                 + "(p:" + Neo4jGroup.NODE + ") "
-                + "RETURN p.id").bindAll(Map.of("id", key)).fetch().all(),
-                "p.id",
-                Neo4jGroup.class,
-                groupCache);
+                + "RETURN p.id").bindAll(Map.of("id", key)).fetch().all();
+        return result.stream().map(found -> found.get("p.id").toString());
+    }
+
+    @Override
+    public List<Group> findManagedGroups(final String key) {
+        List<Group> result = new ArrayList<>();
+
+        // (a) see UserDAO#findManagedGroups
+        findManagedGroupKeys(key).forEach(group -> 
groupDAO.findById(group).ifPresent(result::add));
+
+        // (b) see UserDAO#findManagedGroups
+        findUMembershipGroups(key).forEach(g -> 
result.addAll(groupDAO.findManagedGroups(g)));
+
+        return result.stream().distinct().toList();
     }
 
     @Override
     public List<AnyObject> findManagedAnyObjects(final String key) {
         List<AnyObject> result = new ArrayList<>();
 
-        // 1. anyObjects having uManager set to the requested user
+        // (a) see UserDAO#findManagedAnyObjects
         result.addAll(toList(neo4jClient.query(
                 "MATCH (n:" + Neo4jUser.NODE + " {id: $id})-"
                 + "[:" + AbstractAny.USER_MANAGER_REL + "]-"
@@ -223,14 +262,20 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User, Neo4jUser> impleme
                 Neo4jAnyObject.class,
                 anyObjectCache));
 
-        // 2. anyObject members of groups having uManager set to the requested 
user
-        findManagedGroups(key).forEach(group -> 
groupDAO.findAMembers(group.getKey()).
-                forEach(member -> findById(member, Neo4jAnyObject.class, 
anyObjectCache).
-                ifPresent(result::add)));
+        // (b) see UserDAO#findManagedAnyObjects
+        findManagedGroupKeys(key).forEach(group -> 
groupDAO.findAMembers(group).
+                forEach(m -> findById(m, Neo4jAnyObject.class, 
anyObjectCache).ifPresent(result::add)));
+
+        Collection<Map<String, Object>> membershipQueryResult = 
neo4jClient.query(
+                "MATCH (u:" + Neo4jAnyObject.NODE + " {id: $key})-[]-"
+                + "(n:" + Neo4jAMembership.NODE + ")-[]-"
+                + "(g:" + Neo4jGroup.NODE + ") "
+                + "RETURN g.id").bindAll(Map.of("key", key)).fetch().all();
+        Stream<String> groups = membershipQueryResult.stream().map(found -> 
found.get("g.id").toString());
 
-        // 3. anyObject managed by groups the requested user is member of
-        findById(key).ifPresent(user -> user.getMemberships().
-                forEach(m -> 
result.addAll(groupDAO.findManagedAnyObjects(m.getRightEnd().getKey()))));
+        // (c) see UserDAO#findManagedAnyObjects
+        // (d) see UserDAO#findManagedAnyObjects
+        groups.map(String.class::cast).forEach(group -> 
result.addAll(groupDAO.findManagedAnyObjects(group)));
 
         return result.stream().distinct().toList();
     }
@@ -316,25 +361,25 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User, Neo4jUser> impleme
         neo4jTemplate.findById(user.getKey(), 
Neo4jUser.class).ifPresent(before -> {
             before.getRoles().stream().filter(role -> 
!user.getRoles().contains(role)).
                     forEach(role -> deleteRelationship(
-                    Neo4jUser.NODE,
-                    Neo4jRole.NODE,
-                    user.getKey(),
-                    role.getKey(),
-                    Neo4jUser.ROLE_MEMBERSHIP_REL));
+                            Neo4jUser.NODE,
+                            Neo4jRole.NODE,
+                            user.getKey(),
+                            role.getKey(),
+                            Neo4jUser.ROLE_MEMBERSHIP_REL));
             before.getResources().stream().filter(resource -> 
!user.getResources().contains(resource)).
                     forEach(resource -> deleteRelationship(
-                    Neo4jUser.NODE,
-                    Neo4jExternalResource.NODE,
-                    user.getKey(),
-                    resource.getKey(),
-                    Neo4jUser.USER_RESOURCE_REL));
+                            Neo4jUser.NODE,
+                            Neo4jExternalResource.NODE,
+                            user.getKey(),
+                            resource.getKey(),
+                            Neo4jUser.USER_RESOURCE_REL));
             before.getAuxClasses().stream().filter(auxClass -> 
!user.getAuxClasses().contains(auxClass)).
                     forEach(auxClass -> deleteRelationship(
-                    Neo4jUser.NODE,
-                    Neo4jAnyTypeClass.NODE,
-                    user.getKey(),
-                    auxClass.getKey(),
-                    Neo4jUser.USER_AUX_CLASSES_REL));
+                            Neo4jUser.NODE,
+                            Neo4jAnyTypeClass.NODE,
+                            user.getKey(),
+                            auxClass.getKey(),
+                            Neo4jUser.USER_AUX_CLASSES_REL));
             if (before.getuManager() != null && user.getuManager() == null) {
                 deleteRelationship(
                         Neo4jUser.NODE,
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 7510fd30a9..1bb2e80edc 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
@@ -141,33 +141,6 @@ public class GroupTest extends AbstractTest {
         });
     }
 
-    @Test
-    public void findByUManager() {
-        Group group = 
groupDAO.findById("ebf97068-aa4b-4a85-9f01-680e8c4cf227").orElseThrow();
-
-        User user = 
userDAO.findById("823074dc-d280-436d-a7dd-07399fae48ec").orElseThrow();
-
-        assertEquals(user, group.getuManager());
-
-        List<Group> managed = userDAO.findManagedGroups(user.getKey());
-        assertFalse(managed.isEmpty());
-        assertEquals(1, managed.size());
-        assertTrue(managed.contains(group));
-    }
-
-    @Test
-    public void findByGManager() {
-        Group root = groupDAO.findByName("root").orElseThrow();
-        Group group = entityFactory.newEntity(Group.class);
-        group.setRealm(realmDAO.getRoot());
-        group.setName("error");
-        group.setgManager(root);
-        group = groupDAO.save(group);
-
-        List<Group> managed = groupDAO.findManagedGroups(root.getKey());
-        assertEquals(List.of(group), managed);
-    }
-
     @Test
     public void create() {
         Group group = entityFactory.newEntity(Group.class);
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/ManagerTest.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/ManagerTest.java
new file mode 100644
index 0000000000..a85728aeb4
--- /dev/null
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/ManagerTest.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.neo4j.outer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.neo4j.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+class ManagerTest extends AbstractTest {
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private GroupDAO groupDAO;
+
+    @Autowired
+    private RealmDAO realmDAO;
+
+    /**
+     * (a) U2 has uManager set to U1
+     */
+    @Test
+    void findUManagedUsersA() {
+        User vivaldi = userDAO.findByUsername("vivaldi").orElseThrow();
+        assertNull(vivaldi.getgManager());
+        assertNull(vivaldi.getuManager());
+
+        User rossini = userDAO.findByUsername("rossini").orElseThrow();
+        assertFalse(userDAO.isManager(rossini.getKey()));
+
+        vivaldi.setuManager(rossini);
+        vivaldi = userDAO.save(vivaldi);
+        assertEquals(rossini, vivaldi.getuManager());
+        assertTrue(userDAO.isManager(rossini.getKey()));
+
+        List<User> managed = userDAO.findManagedUsers(rossini.getKey());
+        assertEquals(List.of(vivaldi), managed);
+    }
+
+    /**
+     * (b) G2 has uManager set to U1
+     */
+    @Test
+    void findUManagedUsersB() {
+        Group artDirector = groupDAO.findByName("artDirector").orElseThrow();
+        assertNull(artDirector.getuManager());
+        assertNull(artDirector.getgManager());
+
+        User vivaldi = userDAO.findByUsername("vivaldi").orElseThrow();
+        assertFalse(userDAO.isManager(vivaldi.getKey()));
+
+        artDirector.setuManager(vivaldi);
+        artDirector = groupDAO.save(artDirector);
+        assertEquals(vivaldi, artDirector.getuManager());
+        assertTrue(userDAO.isManager(vivaldi.getKey()));
+
+        User puccini = userDAO.findByUsername("puccini").orElseThrow();
+        assertNull(puccini.getgManager());
+        assertNull(puccini.getuManager());
+        assertTrue(puccini.getMembership(artDirector.getKey()).isPresent());
+
+        List<User> managed = userDAO.findManagedUsers(vivaldi.getKey());
+        assertEquals(List.of(puccini), managed);
+    }
+
+    /**
+     * (c) U2 has gManager set to G1
+     */
+    @Test
+    void findUManagedUsersC() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+        assertFalse(userDAO.isManager(bellini.getKey()));
+
+        User rossini = userDAO.findByUsername("rossini").orElseThrow();
+        assertNull(rossini.getgManager());
+        assertNull(rossini.getuManager());
+
+        rossini.setgManager(managingDirector);
+        rossini = userDAO.save(rossini);
+        assertEquals(managingDirector, rossini.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        List<User> managed = userDAO.findManagedUsers(bellini.getKey());
+        assertEquals(List.of(rossini), managed);
+    }
+
+    /**
+     * (d) G2 has gManager set to G1
+     */
+    @Test
+    void findUManagedUsersD() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+        assertFalse(userDAO.isManager(bellini.getKey()));
+
+        Group otherchild = groupDAO.findByName("otherchild").orElseThrow();
+        assertNull(otherchild.getgManager());
+        assertNull(otherchild.getuManager());
+
+        User rossini = userDAO.findByUsername("rossini").orElseThrow();
+        assertTrue(rossini.getMembership(otherchild.getKey()).isPresent());
+        rossini.setuManager(null);
+        rossini.setgManager(null);
+        rossini = userDAO.save(rossini);
+        assertNull(rossini.getgManager());
+        assertNull(rossini.getuManager());
+
+        otherchild.setgManager(managingDirector);
+        otherchild = groupDAO.save(otherchild);
+        assertEquals(managingDirector, otherchild.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        List<User> managed = userDAO.findManagedUsers(bellini.getKey());
+        assertEquals(List.of(rossini), managed);
+    }
+
+    /**
+     * (a) G2 has uManager set to U1 - see UserDAO#findManagedGroups
+     */
+    @Test
+    void findUManagedGroupsA() {
+        Group director = groupDAO.findByName("director").orElseThrow();
+        User puccini = userDAO.findByUsername("puccini").orElseThrow();
+        assertEquals(puccini, director.getuManager());
+        assertTrue(userDAO.isManager(puccini.getKey()));
+
+        List<Group> managed = userDAO.findManagedGroups(puccini.getKey());
+        assertEquals(List.of(director), managed);
+    }
+
+    /**
+     * (b) G2 has gManager set to G1 - see UserDAO#findManagedGroups
+     */
+    @Test
+    void findUManagedGroupsB() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        Group root = groupDAO.findByName("root").orElseThrow();
+        assertNull(root.getgManager());
+        assertNull(root.getuManager());
+
+        root.setgManager(managingDirector);
+        root = groupDAO.save(root);
+        assertEquals(managingDirector, root.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+
+        List<Group> managed = userDAO.findManagedGroups(bellini.getKey());
+        assertEquals(List.of(root), managed);
+    }
+
+    /**
+     * (a) U has gManager set to G1
+     */
+    @Test
+    void findGManagedUsersA() {
+        Group citizen = groupDAO.findByName("citizen").orElseThrow();
+        assertFalse(groupDAO.isManager(citizen.getKey()));
+
+        User verdi = userDAO.findByUsername("verdi").orElseThrow();
+        assertNull(verdi.getgManager());
+        assertNull(verdi.getuManager());
+
+        verdi.setgManager(citizen);
+        verdi = userDAO.save(verdi);
+        assertEquals(citizen, verdi.getgManager());
+        assertTrue(groupDAO.isManager(citizen.getKey()));
+
+        List<User> managed = groupDAO.findManagedUsers(citizen.getKey());
+        assertEquals(List.of(verdi), managed);
+    }
+
+    /**
+     * (b) G2 has gManager set to G1
+     */
+    @Test
+    void findGManagedUsersB() {
+        Group managingDirector = 
groupDAO.findByName("managingDirector").orElseThrow();
+        assertFalse(groupDAO.isManager(managingDirector.getKey()));
+
+        User bellini = userDAO.findByUsername("bellini").orElseThrow();
+        
assertTrue(bellini.getMembership(managingDirector.getKey()).isPresent());
+
+        Group citizen = groupDAO.findByName("citizen").orElseThrow();
+        assertNull(citizen.getgManager());
+        assertNull(citizen.getuManager());
+
+        User verdi = userDAO.findByUsername("verdi").orElseThrow();
+        assertTrue(verdi.getMembership(citizen.getKey()).isPresent());
+
+        citizen.setgManager(managingDirector);
+        citizen = groupDAO.save(citizen);
+        assertEquals(managingDirector, citizen.getgManager());
+        assertTrue(groupDAO.isManager(managingDirector.getKey()));
+
+        List<User> managed = 
groupDAO.findManagedUsers(managingDirector.getKey());
+        assertEquals(List.of(verdi), managed);
+    }
+
+    /**
+     * (a) G2 has gManager set to G1 - see GroupDAO#findManagedGroups
+     */
+    @Test
+    void findGManagedGroups() {
+        Group root = groupDAO.findByName("root").orElseThrow();
+        assertFalse(groupDAO.isManager(root.getKey()));
+
+        Group group = entityFactory.newEntity(Group.class);
+        group.setRealm(realmDAO.getRoot());
+        group.setName("error");
+        group.setgManager(root);
+        group = groupDAO.save(group);
+        assertEquals(root, group.getgManager());
+        assertTrue(groupDAO.isManager(root.getKey()));
+
+        List<Group> managed = groupDAO.findManagedGroups(root.getKey());
+        assertEquals(List.of(group), managed);
+    }
+}
diff --git 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
index 24c5d44595..819e69067a 100644
--- 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
+++ 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
@@ -205,7 +205,7 @@ public class SelfKeymasterContext {
         openapiFeature.setUseContextBasedConfig(true);
         openapiFeature.setTitle("Apache Syncope Self Keymaster");
         openapiFeature.setVersion(version);
-        openapiFeature.setDescription("Apache Syncope Self Keymaster" + 
version);
+        openapiFeature.setDescription("Apache Syncope Self Keymaster " + 
version);
         openapiFeature.setContactName("The Apache Syncope community");
         openapiFeature.setContactEmail("[email protected]");
         openapiFeature.setContactUrl("https://syncope.apache.org";);
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index b87d9e9d7e..2d5d907840 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -36,6 +36,7 @@ import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.math.NumberUtils;
 import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableBoolean;
 import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.keymaster.client.api.StandardConfParams;
@@ -570,34 +571,44 @@ public class AuthDataAccessor {
 
         // Give role entitlements
         userDAO.findAllRoles(user).forEach(role -> role.getEntitlements().
-                forEach(e -> populateEntForRealms(entForRealms, e, 
role.getRealms().stream().map(Realm::getFullPath).
-                        toList())));
+                forEach(e -> populateEntForRealms(
+                        entForRealms, e, 
role.getRealms().stream().map(Realm::getFullPath).toList())));
 
         // Give manager entitlements
-        if (userDAO.isManager(user.getKey())) {
+        MutableBoolean manager = new MutableBoolean(false);
+        userDAO.findManagedUsers(user.getKey()).forEach(managedUser -> {
+            USER_MANAGER_ENTITLEMENTS.forEach(e -> populateEntForRealms(
+                    entForRealms, e, Set.of(new RealmUtils.ManagerRealm(
+                            managedUser.getRealm().getFullPath(),
+                            AnyTypeKind.USER,
+                            managedUser.getKey()).output())));
+            manager.setTrue();
+        });
+
+        userDAO.findManagedGroups(user.getKey()).forEach(group -> {
+            GROUP_MANAGER_ENTITLEMENTS.forEach(e -> populateEntForRealms(
+                    entForRealms, e, Set.of(new RealmUtils.ManagerRealm(
+                            group.getRealm().getFullPath(),
+                            AnyTypeKind.GROUP,
+                            group.getKey()).output())));
+            manager.setTrue();
+        });
+
+        userDAO.findManagedAnyObjects(user.getKey()).forEach(anyObject -> {
+            
ANYOBJECT_MANAGER_ENTITLEMENTS.apply(anyObject.getType().getKey()).forEach(e -> 
populateEntForRealms(
+                    entForRealms, e, Set.of(
+                            new RealmUtils.ManagerRealm(
+                                    anyObject.getRealm().getFullPath(),
+                                    AnyTypeKind.ANY_OBJECT,
+                                    anyObject.getKey()).output())));
+            manager.setTrue();
+        });
+
+        if (manager.isTrue()) {
             BASE_MANAGER_ENTITLEMENTS.forEach(e -> populateEntForRealms(
                     entForRealms, e, SyncopeConstants.FULL_ADMIN_REALMS));
         }
 
-        userDAO.findManagedUsers(user.getKey()).forEach(managedUser -> 
USER_MANAGER_ENTITLEMENTS.
-                forEach(e -> populateEntForRealms(entForRealms, e, Set.of(new 
RealmUtils.ManagerRealm(
-                        managedUser.getRealm().getFullPath(),
-                        AnyTypeKind.USER,
-                        managedUser.getKey()).output()))));
-
-        userDAO.findManagedGroups(user.getKey()).forEach(group -> 
GROUP_MANAGER_ENTITLEMENTS.
-                forEach(e -> populateEntForRealms(entForRealms, e, Set.of(new 
RealmUtils.ManagerRealm(
-                        group.getRealm().getFullPath(),
-                        AnyTypeKind.GROUP,
-                        group.getKey()).output()))));
-
-        userDAO.findManagedAnyObjects(user.getKey()).forEach(anyObject -> 
ANYOBJECT_MANAGER_ENTITLEMENTS.
-                apply(anyObject.getType().getKey()).forEach(e -> 
populateEntForRealms(entForRealms, e, Set.of(
-                new RealmUtils.ManagerRealm(
-                        anyObject.getRealm().getFullPath(),
-                        AnyTypeKind.ANY_OBJECT,
-                        anyObject.getKey()).output()))));
-
         return buildAuthorities(entForRealms);
     }
 
diff --git a/pom.xml b/pom.xml
index e174ace0e5..512940dbf6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2015,33 +2015,33 @@ under the License.
         <groupId>org.apache.rat</groupId>
         <artifactId>apache-rat-plugin</artifactId>
         <configuration>
-          <excludes>
-            <exclude>**/nbactions.xml</exclude>
-            <exclude>**/nb-configuration.xml</exclude>
-            <exclude>**/META-INF/cxf/**</exclude>
-            <exclude>**/META-INF/services/**</exclude>
-            <exclude>**/META-INF/MANIFEST.MF</exclude>
-            <exclude>**/*.csv</exclude>
-            <exclude>**/archetype-resources/**</exclude>
-            <exclude>**/adminlte*</exclude>
-            <exclude>**/dataTables.bootstrap5.min.css</exclude>
-            <exclude>**/goal.txt</exclude>
-            <exclude>**/rat.txt</exclude>
-            <exclude>**/build-copy-javadoc-files.xml</exclude>
-            <exclude>**/maven-eclipse.xml</exclude>
-            <exclude>**/*.iml</exclude>
-            <exclude>**/*.log</exclude>
-            <exclude>**/.externalToolBuilders/**</exclude>
-            <exclude>.git/**</exclude>
-            <exclude>.idea/**</exclude>
-            <exclude>**/.settings/**</exclude>
-            <exclude>**/.*</exclude>
-            <exclude>**/*.lst</exclude>
-            <exclude>**/*.json</exclude>
-            <exclude>**/*font*awesome*</exclude>
-            <exclude>**/banner.txt</exclude>
-            <exclude>**/target/**</exclude>
-          </excludes>
+          <inputExcludes>
+            <inputExclude>**/nbactions.xml</inputExclude>
+            <inputExclude>**/nb-configuration.xml</inputExclude>
+            <inputExclude>**/META-INF/cxf/**</inputExclude>
+            <inputExclude>**/META-INF/services/**</inputExclude>
+            <inputExclude>**/META-INF/MANIFEST.MF</inputExclude>
+            <inputExclude>**/*.csv</inputExclude>
+            <inputExclude>**/archetype-resources/**</inputExclude>
+            <inputExclude>**/adminlte*</inputExclude>
+            <inputExclude>**/dataTables.bootstrap5.min.css</inputExclude>
+            <inputExclude>**/goal.txt</inputExclude>
+            <inputExclude>**/rat.txt</inputExclude>
+            <inputExclude>**/build-copy-javadoc-files.xml</inputExclude>
+            <inputExclude>**/maven-eclipse.xml</inputExclude>
+            <inputExclude>**/*.iml</inputExclude>
+            <inputExclude>**/*.log</inputExclude>
+            <inputExclude>**/.externalToolBuilders/**</inputExclude>
+            <inputExclude>.git/**</inputExclude>
+            <inputExclude>.idea/**</inputExclude>
+            <inputExclude>**/.settings/**</inputExclude>
+            <inputExclude>**/.*</inputExclude>
+            <inputExclude>**/*.lst</inputExclude>
+            <inputExclude>**/*.json</inputExclude>
+            <inputExclude>**/*font*awesome*</inputExclude>
+            <inputExclude>**/banner.txt</inputExclude>
+            <inputExclude>**/target/**</inputExclude>
+          </inputExcludes>
         </configuration>
         <executions>
           <execution>

Reply via email to