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>
