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

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


The following commit(s) were added to refs/heads/4_0_X by this push:
     new 5074b57609 Ensure consistency of startsWith predicates about Realms 
(#1489)
5074b57609 is described below

commit 5074b576090c29eca21b450676399e80dc9417b2
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Aug 12 11:07:43 2026 +0200

    Ensure consistency of startsWith predicates about Realms (#1489)
---
 .../apache/syncope/core/logic/ConnectorLogic.java  |  4 +--
 .../apache/syncope/core/logic/ResourceLogic.java   |  5 ++--
 .../org/apache/syncope/core/logic/RealmLogic.java  |  8 +++---
 .../org/apache/syncope/core/logic/TaskLogic.java   |  5 ++--
 .../core/persistence/api/utils/RealmUtils.java     | 33 ++++++++++++----------
 .../core/persistence/api/utils/RealmUtilsTest.java | 12 ++++----
 .../jpa/dao/AbstractJPAAnySearchDAO.java           |  2 +-
 .../jpa/dao/repo/AnyObjectRepoExtImpl.java         |  2 +-
 .../jpa/dao/repo/ConnInstanceRepoExtImpl.java      |  9 +++---
 .../jpa/dao/repo/ExternalResourceRepoExtImpl.java  |  5 ++--
 .../persistence/jpa/dao/repo/GroupRepoExtImpl.java |  2 +-
 .../persistence/jpa/dao/repo/UserRepoExtImpl.java  |  2 +-
 .../persistence/neo4j/dao/Neo4jAnySearchDAO.java   |  2 +-
 .../neo4j/dao/repo/AnyObjectRepoExtImpl.java       |  2 +-
 .../neo4j/dao/repo/ConnInstanceRepoExtImpl.java    | 11 ++++----
 .../dao/repo/ExternalResourceRepoExtImpl.java      |  5 ++--
 .../neo4j/dao/repo/GroupRepoExtImpl.java           |  2 +-
 .../neo4j/dao/repo/UserRepoExtImpl.java            |  2 +-
 .../dao/ElasticsearchAnySearchDAO.java             |  2 +-
 .../opensearch/dao/OpenSearchAnySearchDAO.java     |  2 +-
 20 files changed, 62 insertions(+), 55 deletions(-)

diff --git 
a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
 
b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
index 97dce4454b..3cc2017c17 100644
--- 
a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
+++ 
b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
@@ -80,8 +80,8 @@ public class ConnectorLogic extends 
AbstractTransactionalLogic<ConnInstanceTO> {
         this.binder = binder;
     }
 
-    protected void securityChecks(final Set<String> effectiveRealms, final 
String realm, final String key) {
-        if (effectiveRealms.stream().noneMatch(realm::startsWith)) {
+    protected void securityChecks(final Set<String> realms, final String 
realm, final String key) {
+        if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
             throw new DelegatedAdministrationException(realm, 
ConnInstance.class.getSimpleName(), key);
         }
     }
diff --git 
a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java 
b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
index d0cc8b7fcf..0671cc44de 100644
--- 
a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
+++ 
b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
@@ -107,9 +107,8 @@ public class ResourceLogic extends 
AbstractTransactionalLogic<ResourceTO> {
         this.anyUtilsFactory = anyUtilsFactory;
     }
 
-    protected void securityChecks(final Set<String> effectiveRealms, final 
String realm, final String key) {
-        boolean authorized = 
effectiveRealms.stream().anyMatch(realm::startsWith);
-        if (!authorized) {
+    protected void securityChecks(final Set<String> realms, final String 
realm, final String key) {
+        if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
             throw new DelegatedAdministrationException(realm, 
ExternalResource.class.getSimpleName(), key);
         }
     }
diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
index bf297dac35..f8c9bc57d6 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
@@ -50,6 +50,7 @@ import 
org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.api.search.SyncopePage;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.data.RealmDataBinder;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
@@ -110,9 +111,8 @@ public class RealmLogic extends 
AbstractTransactionalLogic<RealmTO> {
         this.taskExecutor = taskExecutor;
     }
 
-    protected void securityChecks(final Set<String> effectiveRealms, final 
String realm) {
-        boolean authorized = 
effectiveRealms.stream().anyMatch(realm::startsWith);
-        if (!authorized) {
+    protected void securityChecks(final Set<String> realms, final String 
realm) {
+        if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
             throw new DelegatedAdministrationException(
                     realm, User.class.getSimpleName(), 
AuthContextUtils.getUsername());
         }
@@ -143,7 +143,7 @@ public class RealmLogic extends 
AbstractTransactionalLogic<RealmTO> {
                 getOrDefault(IdRepoEntitlement.REALM_SEARCH, Set.of());
         List<RealmTO> result = realmSearchDAO.search(baseRealms, 
effectiveCond, pageable).stream().
                 map(realm -> binder.getRealmTO(
-                realm, authorizations.stream().anyMatch(auth -> 
realm.getFullPath().startsWith(auth)))).
+                realm, 
RealmUtils.SubtreePredicate.of(authorizations).test(realm.getFullPath()))).
                 sorted(Comparator.comparing(RealmTO::getFullPath)).
                 toList();
 
diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index b4219713f0..b2356237e5 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -65,6 +65,7 @@ import 
org.apache.syncope.core.persistence.api.entity.task.TaskUtils;
 import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 import org.apache.syncope.core.persistence.api.search.SyncopePage;
 import org.apache.syncope.core.persistence.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
 import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
 import org.apache.syncope.core.provisioning.api.job.JobNamer;
@@ -127,8 +128,8 @@ public class TaskLogic extends 
AbstractExecutableLogic<TaskTO> {
     }
 
     protected void securityChecks(final String entitlement, final String 
realm) {
-        Set<String> authRealms = 
AuthContextUtils.getAuthorizations().getOrDefault(entitlement, Set.of());
-        if (authRealms.isEmpty() || 
authRealms.stream().noneMatch(realm::startsWith)) {
+        Set<String> realms = 
AuthContextUtils.getAuthorizations().getOrDefault(entitlement, Set.of());
+        if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
             throw new DelegatedAdministrationException(realm, 
MacroTask.class.getSimpleName(), null);
         }
     }
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/utils/RealmUtils.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/utils/RealmUtils.java
index cf28cf5838..b7bfe6c3e3 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/utils/RealmUtils.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/utils/RealmUtils.java
@@ -28,8 +28,8 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.Strings;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 
@@ -53,9 +53,9 @@ public class RealmUtils {
         boolean dontAdd = false;
         Set<String> toRemove = new HashSet<>();
         for (String realm : realms) {
-            if (newRealm.startsWith(realm)) {
+            if (subtree(newRealm, realm)) {
                 dontAdd = true;
-            } else if (realm.startsWith(newRealm)) {
+            } else if (subtree(realm, newRealm)) {
                 toRemove.add(realm);
             }
         }
@@ -86,40 +86,43 @@ public class RealmUtils {
         }
     }
 
-    private static class StartsWithPredicate implements Predicate<String> {
+    public static final class SubtreePredicate implements Predicate<String> {
 
-        private final Collection<String> targets;
+        public static SubtreePredicate of(final Collection<String> targets) {
+            return new SubtreePredicate(targets);
+        }
+
+        private final Collection<String> candidates;
 
-        StartsWithPredicate(final Collection<String> targets) {
-            this.targets = targets;
+        private SubtreePredicate(final Collection<String> candidates) {
+            this.candidates = candidates;
         }
 
         @Override
         public boolean test(final String realm) {
-            return targets.stream().anyMatch(realm::startsWith);
+            return candidates.stream().anyMatch(candidate -> subtree(realm, 
candidate));
         }
     }
 
-    private static final Predicate<String> DYN_REALMS_PREDICATE = r -> 
!r.startsWith(SyncopeConstants.ROOT_REALM);
+    public static boolean subtree(final String realm, final String prefix) {
+        return realm.equals(prefix) || 
realm.startsWith(Strings.CS.appendIfMissing(prefix, "/"));
+    }
 
     public static Set<String> getEffective(final Set<String> allowedRealms, 
final String requestedRealm) {
         NormalizedRealms normalized = NormalizedRealms.of(allowedRealms);
 
         Set<String> requested = Set.of(requestedRealm);
 
-        StartsWithPredicate normalizedFilter = new 
StartsWithPredicate(normalized.realms());
-        StartsWithPredicate requestedFilter = new 
StartsWithPredicate(requested);
-
         Set<String> effective = new HashSet<>();
-        
effective.addAll(requested.stream().filter(normalizedFilter).collect(Collectors.toSet()));
-        
effective.addAll(normalized.realms().stream().filter(requestedFilter).collect(Collectors.toSet()));
+        
effective.addAll(requested.stream().filter(SubtreePredicate.of(normalized.realms())).toList());
+        
effective.addAll(normalized.realms().stream().filter(SubtreePredicate.of(requested)).toList());
 
         // includes group ownership
         effective.addAll(normalized.groupOwnerRealms());
 
         // includes dynamic realms
         if (allowedRealms != null) {
-            
effective.addAll(allowedRealms.stream().filter(DYN_REALMS_PREDICATE).collect(Collectors.toSet()));
+            effective.addAll(allowedRealms.stream().filter(r -> 
!r.startsWith(SyncopeConstants.ROOT_REALM)).toList());
         }
 
         return effective;
diff --git 
a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/utils/RealmUtilsTest.java
 
b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/utils/RealmUtilsTest.java
index 8e29befdba..a09e560760 100644
--- 
a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/utils/RealmUtilsTest.java
+++ 
b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/utils/RealmUtilsTest.java
@@ -47,16 +47,16 @@ public class RealmUtilsTest extends AbstractTest {
     @Test
     public void normalizingAddTo() {
         Set<String> realms = new HashSet<>();
-        realms.add("realm1");
-        realms.add("realm2");
-        String newRealm = "realm123";
+        realms.add("/realm1");
+        realms.add("/realm2");
+        String newRealm = "/realm1/23";
         assertFalse(RealmUtils.normalizingAddTo(realms, newRealm));
         assertEquals(2, realms.size());
 
         realms.clear();
-        realms.add("testRealm1");
-        realms.add("realm2");
-        newRealm = "test";
+        realms.add("/test/realm1");
+        realms.add("/realm2");
+        newRealm = "/test";
         assertTrue(RealmUtils.normalizingAddTo(realms, newRealm));
         assertEquals(2, realms.size());
     }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
index 231052dea3..666e6481bf 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
@@ -722,7 +722,7 @@ abstract class AbstractJPAAnySearchDAO extends 
AbstractAnySearchDAO {
                 realmKeys.clear();
             }
         } else {
-            if (adminRealms.stream().anyMatch(r -> 
r.startsWith(base.getFullPath()))) {
+            if 
(RealmUtils.SubtreePredicate.of(adminRealms).test(base.getFullPath())) {
                 realmKeys.add(base.getKey());
             }
         }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
index 21d0dc009e..2e2ff480c8 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
@@ -131,7 +131,7 @@ public class AnyObjectRepoExtImpl extends 
AbstractAnyRepoExt<AnyObject> implemen
 
         // 3. check if anyObject is in Realm (or descendants) for which 
AuthContextUtils.getUsername() owns entitlement
         if (!authorized) {
-            authorized = authRealms.stream().anyMatch(realm::startsWith);
+            authorized = 
RealmUtils.SubtreePredicate.of(authRealms).test(realm);
         }
 
         if (!authorized) {
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ConnInstanceRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ConnInstanceRepoExtImpl.java
index a865725bcc..ca29d7947c 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ConnInstanceRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ConnInstanceRepoExtImpl.java
@@ -26,10 +26,12 @@ import org.apache.syncope.common.lib.types.IdMEntitlement;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
 import org.apache.syncope.core.persistence.jpa.entity.JPAConnInstance;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import 
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 public class ConnInstanceRepoExtImpl implements ConnInstanceRepoExt {
 
@@ -51,9 +53,8 @@ public class ConnInstanceRepoExtImpl implements 
ConnInstanceRepoExt {
         }
 
         Set<String> authRealms = 
AuthContextUtils.getAuthorizations().get(IdMEntitlement.CONNECTOR_READ);
-        if (authRealms == null || authRealms.isEmpty()
-                || authRealms.stream().noneMatch(
-                        realm -> 
connInstance.getAdminRealm().getFullPath().startsWith(realm))) {
+        if (CollectionUtils.isEmpty(authRealms)
+                || 
!RealmUtils.SubtreePredicate.of(authRealms).test(connInstance.getAdminRealm().getFullPath()))
 {
 
             throw new DelegatedAdministrationException(
                     connInstance.getAdminRealm().getFullPath(),
@@ -75,7 +76,7 @@ public class ConnInstanceRepoExtImpl implements 
ConnInstanceRepoExt {
                 "SELECT e FROM " + JPAConnInstance.class.getSimpleName() + " 
e", ConnInstance.class);
 
         return query.getResultList().stream().filter(connInstance -> 
authRealms.stream().
-                anyMatch(realm -> 
connInstance.getAdminRealm().getFullPath().startsWith(realm))).
+                anyMatch(realm -> 
RealmUtils.subtree(connInstance.getAdminRealm().getFullPath(), realm))).
                 toList();
     }
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ExternalResourceRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ExternalResourceRepoExtImpl.java
index c8553fed17..099e184600 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ExternalResourceRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ExternalResourceRepoExtImpl.java
@@ -38,6 +38,7 @@ import 
org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
 import org.apache.syncope.core.persistence.jpa.entity.JPAExternalResource;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import 
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
@@ -84,7 +85,7 @@ public class ExternalResourceRepoExtImpl implements 
ExternalResourceRepoExt {
         Set<String> authRealms = 
AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_READ);
         if (authRealms == null || authRealms.isEmpty()
                 || authRealms.stream().noneMatch(realm -> 
resource.getConnector() != null
-                && 
resource.getConnector().getAdminRealm().getFullPath().startsWith(realm))) {
+                && 
RealmUtils.subtree(resource.getConnector().getAdminRealm().getFullPath(), 
realm))) {
 
             throw new DelegatedAdministrationException(
                     resource.getConnector().getAdminRealm().getFullPath(),
@@ -132,7 +133,7 @@ public class ExternalResourceRepoExtImpl implements 
ExternalResourceRepoExt {
 
         return query.getResultList().stream().filter(resource -> 
authRealms.stream().
                 anyMatch(realm -> resource.getConnector() != null
-                && 
resource.getConnector().getAdminRealm().getFullPath().startsWith(realm))).
+                && 
RealmUtils.subtree(resource.getConnector().getAdminRealm().getFullPath(), 
realm))).
                 toList();
     }
 
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 ef5eb91990..bdcb2daecd 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
@@ -125,7 +125,7 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group> implements Group
 
         // 1. check if AuthContextUtils.getUsername() is owner of the group, or
         // if group is in Realm (or descendants) for which 
AuthContextUtils.getUsername() owns entitlement
-        boolean authorized = authRealms.stream().anyMatch(authRealm -> 
realm.startsWith(authRealm)
+        boolean authorized = authRealms.stream().anyMatch(authRealm -> 
RealmUtils.subtree(realm, authRealm)
                 || authRealm.equals(new RealmUtils.GroupOwnerRealm(realm, 
key).output()));
 
         // 2. check if groups is in at least one DynRealm for which 
AuthContextUtils.getUsername() owns entitlement
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 6b5dd8f216..7c2fbd666c 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
@@ -139,7 +139,7 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User> implements UserRep
 
         // 3. check if user is in Realm (or descendants) for which 
AuthContextUtils.getUsername() owns entitlement
         if (!authorized) {
-            authorized = authRealms.stream().anyMatch(realm::startsWith);
+            authorized = 
RealmUtils.SubtreePredicate.of(authRealms).test(realm);
         }
 
         if (!authorized) {
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
index 728e3a1ff0..47271a3347 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
@@ -231,7 +231,7 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO 
{
                 realmKeys.clear();
             }
         } else {
-            if (adminRealms.stream().anyMatch(r -> 
r.startsWith(base.getFullPath()))) {
+            if 
(RealmUtils.SubtreePredicate.of(adminRealms).test(base.getFullPath())) {
                 realmKeys.add(base.getKey());
             }
         }
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
index 6e7d9f0335..e4fd8d8279 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
@@ -159,7 +159,7 @@ public class AnyObjectRepoExtImpl extends 
AbstractAnyRepoExt<AnyObject, Neo4jAny
 
         // 3. check if anyObject is in Realm (or descendants) for which 
AuthContextUtils.getUsername() owns entitlement
         if (!authorized) {
-            authorized = authRealms.stream().anyMatch(realm::startsWith);
+            authorized = 
RealmUtils.SubtreePredicate.of(authRealms).test(realm);
         }
 
         if (!authorized) {
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ConnInstanceRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ConnInstanceRepoExtImpl.java
index 80784d2d8e..cde58eddd7 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ConnInstanceRepoExtImpl.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ConnInstanceRepoExtImpl.java
@@ -24,12 +24,14 @@ import org.apache.syncope.common.lib.types.IdMEntitlement;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
 import org.apache.syncope.core.persistence.neo4j.entity.Neo4jConnInstance;
 import org.apache.syncope.core.persistence.neo4j.spring.NodeValidator;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import 
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.springframework.data.neo4j.core.Neo4jTemplate;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 public class ConnInstanceRepoExtImpl implements ConnInstanceRepoExt {
 
@@ -58,9 +60,8 @@ public class ConnInstanceRepoExtImpl implements 
ConnInstanceRepoExt {
         }
 
         Set<String> authRealms = 
AuthContextUtils.getAuthorizations().get(IdMEntitlement.CONNECTOR_READ);
-        if (authRealms == null || authRealms.isEmpty()
-                || authRealms.stream().noneMatch(
-                        realm -> 
connInstance.getAdminRealm().getFullPath().startsWith(realm))) {
+        if (CollectionUtils.isEmpty(authRealms)
+                || 
!RealmUtils.SubtreePredicate.of(authRealms).test(connInstance.getAdminRealm().getFullPath()))
 {
 
             throw new DelegatedAdministrationException(
                     connInstance.getAdminRealm().getFullPath(),
@@ -79,7 +80,7 @@ public class ConnInstanceRepoExtImpl implements 
ConnInstanceRepoExt {
         }
 
         return 
neo4jTemplate.findAll(Neo4jConnInstance.class).stream().filter(connInstance -> 
authRealms.stream().
-                anyMatch(realm -> 
connInstance.getAdminRealm().getFullPath().startsWith(realm))).
+                anyMatch(realm -> 
RealmUtils.subtree(connInstance.getAdminRealm().getFullPath(), realm))).
                 toList();
     }
 
@@ -95,7 +96,7 @@ public class ConnInstanceRepoExtImpl implements 
ConnInstanceRepoExt {
     public void deleteById(final String key) {
         neo4jTemplate.findById(key, 
Neo4jConnInstance.class).ifPresent(connInstance -> {
             connInstance.getResources().stream().
-            
map(ExternalResource::getKey).toList().forEach(resourceDAO::deleteById);
+                    
map(ExternalResource::getKey).toList().forEach(resourceDAO::deleteById);
 
             neo4jTemplate.deleteById(key, Neo4jConnInstance.class);
         });
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ExternalResourceRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ExternalResourceRepoExtImpl.java
index a0115498e8..a54c1fffbd 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ExternalResourceRepoExtImpl.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/ExternalResourceRepoExtImpl.java
@@ -39,6 +39,7 @@ import 
org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
 import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO;
 import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
 import org.apache.syncope.core.persistence.neo4j.entity.Neo4jConnInstance;
@@ -112,7 +113,7 @@ public class ExternalResourceRepoExtImpl extends 
AbstractDAO implements External
         Set<String> authRealms = 
AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_READ);
         if (authRealms == null || authRealms.isEmpty()
                 || authRealms.stream().noneMatch(realm -> 
resource.getConnector() != null
-                && 
resource.getConnector().getAdminRealm().getFullPath().startsWith(realm))) {
+                && 
RealmUtils.subtree(resource.getConnector().getAdminRealm().getFullPath(), 
realm))) {
 
             throw new DelegatedAdministrationException(
                     resource.getConnector().getAdminRealm().getFullPath(),
@@ -196,7 +197,7 @@ public class ExternalResourceRepoExtImpl extends 
AbstractDAO implements External
                 cache);
         return all.stream().filter(resource -> authRealms.stream().
                 anyMatch(realm -> resource.getConnector() != null
-                && 
resource.getConnector().getAdminRealm().getFullPath().startsWith(realm))).
+                && 
RealmUtils.subtree(resource.getConnector().getAdminRealm().getFullPath(), 
realm))).
                 toList();
     }
 
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 20fab129fa..5c028ed8ef 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
@@ -165,7 +165,7 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
 
         // 1. check if AuthContextUtils.getUsername() is owner of the group, or
         // if group is in Realm (or descendants) for which 
AuthContextUtils.getUsername() owns entitlement
-        boolean authorized = authRealms.stream().anyMatch(authRealm -> 
realm.startsWith(authRealm)
+        boolean authorized = authRealms.stream().anyMatch(authRealm -> 
RealmUtils.subtree(realm, authRealm)
                 || authRealm.equals(new RealmUtils.GroupOwnerRealm(realm, 
key).output()));
 
         // 2. check if groups is in at least one DynRealm for which 
AuthContextUtils.getUsername() owns entitlement
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 37f756fc09..078eabc34d 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
@@ -177,7 +177,7 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User, Neo4jUser> impleme
 
         // 3. check if user is in Realm (or descendants) for which 
AuthContextUtils.getUsername() owns entitlement
         if (!authorized) {
-            authorized = authRealms.stream().anyMatch(realm::startsWith);
+            authorized = 
RealmUtils.SubtreePredicate.of(authRealms).test(realm);
         }
 
         if (!authorized) {
diff --git 
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
 
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
index 82aee8ec6e..f88dbbb5a6 100644
--- 
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
+++ 
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
@@ -160,7 +160,7 @@ public class ElasticsearchAnySearchDAO extends 
AbstractAnySearchDAO {
                 }
             });
         } else {
-            if (adminRealms.stream().anyMatch(r -> 
r.startsWith(base.getFullPath()))) {
+            if 
(RealmUtils.SubtreePredicate.of(adminRealms).test(base.getFullPath())) {
                 queries.add(new Query.Builder().term(QueryBuilders.term().
                         
field("realm").value(base.getKey()).caseInsensitive(false).build()).
                         build());
diff --git 
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
 
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
index e1e7294559..dc9c98e746 100644
--- 
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
+++ 
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
@@ -159,7 +159,7 @@ public class OpenSearchAnySearchDAO extends 
AbstractAnySearchDAO {
                 }
             });
         } else {
-            if (adminRealms.stream().anyMatch(r -> 
r.startsWith(base.getFullPath()))) {
+            if 
(RealmUtils.SubtreePredicate.of(adminRealms).test(base.getFullPath())) {
                 queries.add(new Query.Builder().term(QueryBuilders.term().
                         
field("realm").value(FieldValue.of(base.getKey())).caseInsensitive(false).build()).
                         build());

Reply via email to