You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2022/05/23 12:13:44 UTC

[syncope] branch master updated: [SYNCOPE-1678] Adding recursive boolean parameter to AnyQuery, true b… (#346)

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


The following commit(s) were added to refs/heads/master by this push:
     new 8a2690568f [SYNCOPE-1678] Adding recursive boolean parameter to AnyQuery, true b… (#346)
8a2690568f is described below

commit 8a2690568f7e3653a7112c0bdf795654efb236c5
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Mon May 23 14:13:33 2022 +0200

    [SYNCOPE-1678] Adding recursive boolean parameter to AnyQuery, true b… (#346)
---
 .../syncope/common/rest/api/beans/AnyQuery.java    | 23 +++++-
 .../common/rest/api/service/JAXRSService.java      |  2 +
 .../syncope/core/logic/ReconciliationLogic.java    | 11 ++-
 .../core/logic/ReconciliationLogicTest.java        |  2 +-
 .../syncope/core/logic/AbstractAnyLogic.java       |  1 +
 .../apache/syncope/core/logic/AnyObjectLogic.java  | 11 ++-
 .../org/apache/syncope/core/logic/GroupLogic.java  | 10 ++-
 .../syncope/core/logic/IdRepoLogicContext.java     |  2 +
 .../org/apache/syncope/core/logic/RealmLogic.java  | 15 ++--
 .../apache/syncope/core/logic/SyncopeLogic.java    | 19 ++++-
 .../org/apache/syncope/core/logic/UserLogic.java   | 10 ++-
 .../core/rest/cxf/service/AbstractAnyService.java  |  1 +
 .../rest/cxf/service/AnyObjectServiceTest.java     |  4 +-
 .../core/persistence/api/dao/AnySearchDAO.java     | 22 +++++-
 .../persistence/jpa/dao/PGJPAJSONAnySearchDAO.java | 16 +++-
 .../persistence/jpa/dao/AbstractAnySearchDAO.java  | 26 ++++--
 .../core/persistence/jpa/dao/JPAAnySearchDAO.java  | 70 ++++++++++------
 .../core/persistence/jpa/dao/JPAGroupDAO.java      | 10 ++-
 .../core/persistence/jpa/inner/AnySearchTest.java  | 68 ++++++++++------
 .../core/persistence/jpa/outer/AnySearchTest.java  |  7 +-
 .../java/job/report/GroupReportlet.java            | 41 ++++++----
 .../java/job/report/ReconciliationReportlet.java   | 31 +++++++-
 .../java/job/report/UserReportlet.java             | 45 +++++++----
 .../pushpull/DefaultRealmPullResultHandler.java    |  9 ++-
 .../java/pushpull/PushJobDelegate.java             |  4 +
 .../jpa/dao/ElasticsearchAnySearchDAO.java         | 92 +++++++++++++++-------
 .../jpa/dao/ElasticsearchAnySearchDAOTest.java     |  9 ++-
 .../apache/syncope/core/logic/SCIMDataBinder.java  |  4 +-
 .../ext/scimv2/cxf/service/AbstractService.java    |  1 +
 .../ext/scimv2/cxf/service/GroupServiceImpl.java   |  4 +-
 .../org/apache/syncope/fit/core/SearchITCase.java  |  7 ++
 pom.xml                                            |  4 +-
 32 files changed, 411 insertions(+), 170 deletions(-)

diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java
index fa5822336f..b9583785b0 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java
@@ -21,6 +21,7 @@ package org.apache.syncope.common.rest.api.beans;
 import io.swagger.v3.oas.annotations.ExternalDocumentation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Optional;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.QueryParam;
 import org.apache.commons.lang3.builder.EqualsBuilder;
@@ -28,8 +29,6 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
 
-import java.util.Optional;
-
 public class AnyQuery extends AbstractQuery {
 
     private static final long serialVersionUID = -6736562952418964707L;
@@ -41,6 +40,11 @@ public class AnyQuery extends AbstractQuery {
             return new AnyQuery();
         }
 
+        public Builder recursive(final boolean recursive) {
+            getInstance().setRecursive(recursive);
+            return this;
+        }
+
         public Builder details(final boolean details) {
             getInstance().setDetails(details);
             return this;
@@ -60,6 +64,8 @@ public class AnyQuery extends AbstractQuery {
 
     private String realm;
 
+    private Boolean recursive;
+
     private Boolean details;
 
     private String fiql;
@@ -79,6 +85,19 @@ public class AnyQuery extends AbstractQuery {
         this.realm = realm;
     }
 
+    @Parameter(name = JAXRSService.PARAM_RECURSIVE, description = "whether search results shall be returned from "
+            + "given realm and all children realms, or just the given realm", schema =
+            @Schema(implementation = Boolean.class))
+    public Boolean getRecursive() {
+        return Optional.ofNullable(recursive).orElse(Boolean.TRUE);
+    }
+
+    @QueryParam(JAXRSService.PARAM_RECURSIVE)
+    @DefaultValue("true")
+    public void setRecursive(final Boolean recursive) {
+        this.recursive = recursive;
+    }
+
     @Parameter(name = JAXRSService.PARAM_DETAILS, description = "whether detailed information is to be included, "
             + "if applicable, about virtual attributes, (dynamic) roles, privileges, relationships, "
             + "(dynamic) memberships or linked accounts", schema =
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java
index 6ebdfd3baf..51a59cc576 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java
@@ -40,6 +40,8 @@ public interface JAXRSService {
 
     String PARAM_REALM = "realm";
 
+    String PARAM_RECURSIVE = "recursive";
+
     String PARAM_DETAILS = "details";
 
     String PARAM_CONNID_PAGED_RESULTS_COOKIE = "connIdPagedResultsCookie";
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index ef3deee89b..a7532c4363 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -68,6 +68,7 @@ import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.provisioning.api.ConnectorManager;
 import org.apache.syncope.core.provisioning.api.pushpull.ConstantReconFilterBuilder;
@@ -568,6 +569,9 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
                 entitlement = IdRepoEntitlement.USER_SEARCH;
         }
 
+        Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
+                orElseThrow(() -> new NotFoundException("Realm " + realm));
+
         Set<String> adminRealms = RealmUtils.getEffective(AuthContextUtils.getAuthorizations().get(entitlement), realm);
         SearchCond effectiveCond = searchCond == null ? anyUtils.dao().getAllMatchingCond() : searchCond;
 
@@ -575,15 +579,16 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
         if (spec.getIgnorePaging()) {
             matching = new ArrayList<>();
 
-            int count = anySearchDAO.count(adminRealms, effectiveCond, anyType.getKind());
+            int count = anySearchDAO.count(base, true, adminRealms, effectiveCond, anyType.getKind());
             int pages = (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
 
             for (int p = 1; p <= pages; p++) {
-                matching.addAll(anySearchDAO.search(adminRealms, effectiveCond,
+                matching.addAll(anySearchDAO.search(base, true, adminRealms, effectiveCond,
                         p, AnyDAO.DEFAULT_PAGE_SIZE, orderBy, anyType.getKind()));
             }
         } else {
-            matching = anySearchDAO.search(adminRealms, effectiveCond, page, size, orderBy, anyType.getKind());
+            matching = anySearchDAO.search(
+                    base, true, adminRealms, effectiveCond, page, size, orderBy, anyType.getKind());
         }
 
         List<String> columns = new ArrayList<>();
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
index 7fb1f9e731..174d347cbd 100644
--- a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
@@ -89,7 +89,7 @@ public class ReconciliationLogicTest extends AbstractTest {
     @Test
     public void pushToCSV() throws IOException {
         Pair<Integer, List<UserTO>> search = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN,
-                () -> userLogic.search(null, 1, 100, List.of(), SyncopeConstants.ROOT_REALM, false));
+                () -> userLogic.search(null, 1, 100, List.of(), SyncopeConstants.ROOT_REALM, true, false));
         assertNotNull(search);
 
         CSVPushSpec spec = new CSVPushSpec.Builder(AnyTypeKind.USER.name()).ignorePaging(true).
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
index 47b393ec82..7469722d5d 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
@@ -212,6 +212,7 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, C extends AnyCR, U exte
             SearchCond searchCond,
             int page, int size, List<OrderByClause> orderBy,
             String realm,
+            boolean recursive,
             boolean details);
 
     public abstract ProvisioningResult<TO> update(U updateReq, boolean nullPriorityAsync);
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
index 71271cca2f..d26222b469 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
@@ -42,10 +43,12 @@ import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
 import org.apache.syncope.core.provisioning.api.LogicActions;
@@ -98,20 +101,24 @@ public class AnyObjectLogic extends AbstractAnyLogic<AnyObjectTO, AnyObjectCR, A
             final SearchCond searchCond,
             final int page, final int size, final List<OrderByClause> orderBy,
             final String realm,
+            final boolean recursive,
             final boolean details) {
 
         if (searchCond.hasAnyTypeCond() == null) {
             throw new UnsupportedOperationException("Need to specify " + AnyType.class.getSimpleName());
         }
 
+        Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
+                orElseThrow(() -> new NotFoundException("Realm " + realm));
+
         Set<String> authRealms = RealmUtils.getEffective(
                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.SEARCH.getFor(searchCond.hasAnyTypeCond())),
                 realm);
 
-        int count = searchDAO.count(authRealms, searchCond, AnyTypeKind.ANY_OBJECT);
+        int count = searchDAO.count(base, recursive, authRealms, searchCond, AnyTypeKind.ANY_OBJECT);
 
         List<AnyObject> matching = searchDAO.search(
-                authRealms, searchCond, page, size, orderBy, AnyTypeKind.ANY_OBJECT);
+                base, recursive, authRealms, searchCond, page, size, orderBy, AnyTypeKind.ANY_OBJECT);
         List<AnyObjectTO> result = matching.stream().
                 map(anyObject -> binder.getAnyObjectTO(anyObject, details)).
                 collect(Collectors.toList());
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
index dcab7dec61..df16b30c9d 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
@@ -23,6 +23,7 @@ import java.time.OffsetDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
@@ -56,6 +57,7 @@ import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
 import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
@@ -167,17 +169,21 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupCR, GroupUR> {
             final SearchCond searchCond,
             final int page, final int size, final List<OrderByClause> orderBy,
             final String realm,
+            final boolean recursive,
             final boolean details) {
 
+        Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
+                orElseThrow(() -> new NotFoundException("Realm " + realm));
+
         Set<String> authRealms = RealmUtils.getEffective(
                 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_SEARCH), realm);
 
         SearchCond effectiveCond = searchCond == null ? groupDAO.getAllMatchingCond() : searchCond;
 
-        int count = searchDAO.count(authRealms, effectiveCond, AnyTypeKind.GROUP);
+        int count = searchDAO.count(base, recursive, authRealms, effectiveCond, AnyTypeKind.GROUP);
 
         List<Group> matching = searchDAO.search(
-                authRealms, effectiveCond, page, size, orderBy, AnyTypeKind.GROUP);
+                base, recursive, authRealms, effectiveCond, page, size, orderBy, AnyTypeKind.GROUP);
         List<GroupTO> result = matching.stream().
                 map(group -> binder.getGroupTO(group, details)).
                 collect(Collectors.toList());
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
index bbb7d88b2d..a6cbdfcf17 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
@@ -426,6 +426,7 @@ public class IdRepoLogicContext {
     public SyncopeLogic syncopeLogic(
             final ContentExporter exporter,
             final UserWorkflowAdapter uwfAdapter,
+            final RealmDAO realmDAO,
             final AnyTypeDAO anyTypeDAO,
             final GroupDAO groupDAO,
             final ConfParamOps confParamOps,
@@ -435,6 +436,7 @@ public class IdRepoLogicContext {
             final AnyObjectWorkflowAdapter awfAdapter) {
 
         return new SyncopeLogic(
+                realmDAO,
                 anyTypeDAO,
                 groupDAO,
                 anySearchDAO,
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 bd1a0a96f6..1f49db7b45 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
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic;
 import java.lang.reflect.Method;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
@@ -189,12 +190,8 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_DELETE + "')")
     public ProvisioningResult<RealmTO> delete(final String fullPath) {
-        Realm realm = realmDAO.findByFullPath(fullPath);
-        if (realm == null) {
-            LOG.error("Could not find realm '" + fullPath + '\'');
-
-            throw new NotFoundException(fullPath);
-        }
+        Realm realm = Optional.ofNullable(realmDAO.findByFullPath(fullPath)).
+                orElseThrow(() -> new NotFoundException("Realm " + fullPath));
 
         if (!realmDAO.findChildren(realm).isEmpty()) {
             throw SyncopeClientException.build(ClientExceptionType.HasChildren);
@@ -204,9 +201,9 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
         AnyCond keyCond = new AnyCond(AttrCond.Type.ISNOTNULL);
         keyCond.setSchema("key");
         SearchCond allMatchingCond = SearchCond.getLeaf(keyCond);
-        int users = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.USER);
-        int groups = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.GROUP);
-        int anyObjects = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
+        int users = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.USER);
+        int groups = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.GROUP);
+        int anyObjects = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
 
         if (users + groups + anyObjects > 0) {
             SyncopeClientException containedAnys = SyncopeClientException.build(ClientExceptionType.AssociatedAnys);
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
index b90aefd5d8..167840cadf 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
@@ -37,11 +37,13 @@ import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
@@ -55,6 +57,8 @@ import org.springframework.transaction.annotation.Transactional;
 @Transactional(readOnly = true)
 public class SyncopeLogic extends AbstractLogic<EntityTO> {
 
+    protected final RealmDAO realmDAO;
+
     protected final AnyTypeDAO anyTypeDAO;
 
     protected final GroupDAO groupDAO;
@@ -74,6 +78,7 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
     protected final AnyObjectWorkflowAdapter awfAdapter;
 
     public SyncopeLogic(
+            final RealmDAO realmDAO,
             final AnyTypeDAO anyTypeDAO,
             final GroupDAO groupDAO,
             final AnySearchDAO searchDAO,
@@ -84,6 +89,7 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
             final GroupWorkflowAdapter gwfAdapter,
             final AnyObjectWorkflowAdapter awfAdapter) {
 
+        this.realmDAO = realmDAO;
         this.anyTypeDAO = anyTypeDAO;
         this.groupDAO = groupDAO;
         this.searchDAO = searchDAO;
@@ -114,6 +120,9 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
             final int page,
             final int size) {
 
+        Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
+                orElseThrow(() -> new NotFoundException("Realm " + realm));
+
         AssignableCond assignableCond = new AssignableCond();
         assignableCond.setRealmFullPath(realm);
 
@@ -137,16 +146,20 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
             searchCond = SearchCond.getLeaf(assignableCond);
         }
 
-        int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.GROUP);
+        int count = searchDAO.count(base, true, SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.GROUP);
 
         OrderByClause orderByClause = new OrderByClause();
         orderByClause.setField("name");
         orderByClause.setDirection(OrderByClause.Direction.ASC);
         List<Group> matching = searchDAO.search(
+                base,
+                true,
                 SyncopeConstants.FULL_ADMIN_REALMS,
                 searchCond,
-                page, size,
-                List.of(orderByClause), AnyTypeKind.GROUP);
+                page,
+                size,
+                List.of(orderByClause),
+                AnyTypeKind.GROUP);
         List<GroupTO> result = matching.stream().
                 map(group -> groupDataBinder.getGroupTO(group, false)).collect(Collectors.toList());
 
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index 84a53f8d2d..0e7db7416b 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -56,6 +56,7 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 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.provisioning.api.LogicActions;
@@ -144,16 +145,21 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserCR, UserUR> {
             final SearchCond searchCond,
             final int page, final int size, final List<OrderByClause> orderBy,
             final String realm,
+            final boolean recursive,
             final boolean details) {
 
+        Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
+                orElseThrow(() -> new NotFoundException("Realm " + realm));
+
         Set<String> authRealms = RealmUtils.getEffective(
                 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_SEARCH), realm);
 
         SearchCond effectiveCond = searchCond == null ? userDAO.getAllMatchingCond() : searchCond;
 
-        int count = searchDAO.count(authRealms, effectiveCond, AnyTypeKind.USER);
+        int count = searchDAO.count(base, recursive, authRealms, effectiveCond, AnyTypeKind.USER);
 
-        List<User> matching = searchDAO.search(authRealms, effectiveCond, page, size, orderBy, AnyTypeKind.USER);
+        List<User> matching = searchDAO.search(
+                base, recursive, authRealms, effectiveCond, page, size, orderBy, AnyTypeKind.USER);
         List<UserTO> result = matching.stream().
                 map(user -> binder.getUserTO(user, details)).
                 collect(Collectors.toList());
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
index f821a81ce8..d37ab65604 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
@@ -144,6 +144,7 @@ public abstract class AbstractAnyService<TO extends AnyTO, CR extends AnyCR, UR
                     anyQuery.getSize(),
                     getOrderByClauses(anyQuery.getOrderBy()),
                     isAssignableCond ? SyncopeConstants.ROOT_REALM : realm,
+                    anyQuery.getRecursive(),
                     anyQuery.getDetails());
 
             return buildPagedResult(result.getRight(), anyQuery.getPage(), anyQuery.getSize(), result.getLeft());
diff --git a/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java b/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java
index e5213bd572..5078c55fa8 100644
--- a/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java
+++ b/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java
@@ -78,6 +78,7 @@ import org.apache.syncope.core.rest.cxf.RestServiceExceptionMapper;
 import org.apache.syncope.common.lib.jackson.SyncopeJsonMapper;
 import org.apache.syncope.common.lib.jackson.SyncopeXmlMapper;
 import org.apache.syncope.common.lib.jackson.SyncopeYAMLMapper;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -129,7 +130,8 @@ public class AnyObjectServiceTest {
             AnyObjectDAO anyObjectDAO = mock(AnyObjectDAO.class);
 
             AnyObjectLogic logic = mock(AnyObjectLogic.class);
-            when(logic.search(any(SearchCond.class), anyInt(), anyInt(), anyList(), anyString(), anyBoolean())).
+            when(logic.search(
+                    any(SearchCond.class), anyInt(), anyInt(), anyList(), anyString(), anyBoolean(), anyBoolean())).
                     thenAnswer(ic -> {
                         AnyObjectTO printer1 = new AnyObjectTO();
                         printer1.setKey(UUID.randomUUID().toString());
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java
index 8d0de626a9..107932c33e 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java
@@ -24,16 +24,24 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 
 public interface AnySearchDAO extends DAO<Any<?>> {
 
     /**
+     * @param base Realm to start searching from
+     * @param recursive whether search should recursively include results from child Realms
      * @param adminRealms realms for which the caller owns the proper entitlement(s)
      * @param searchCondition the search condition
      * @param kind any object
      * @return size of search result
      */
-    int count(Set<String> adminRealms, SearchCond searchCondition, AnyTypeKind kind);
+    int count(
+            Realm base,
+            boolean recursive,
+            Set<String> adminRealms,
+            SearchCond searchCondition,
+            AnyTypeKind kind);
 
     /**
      * @param searchCondition the search condition
@@ -53,6 +61,8 @@ public interface AnySearchDAO extends DAO<Any<?>> {
     <T extends Any<?>> List<T> search(SearchCond searchCondition, List<OrderByClause> orderBy, AnyTypeKind kind);
 
     /**
+     * @param base Realm to start searching from
+     * @param recursive whether search should recursively include results from child Realms
      * @param adminRealms realms for which the caller owns the proper entitlement(s)
      * @param searchCondition the search condition
      * @param page position of the first result, start from 1
@@ -63,6 +73,12 @@ public interface AnySearchDAO extends DAO<Any<?>> {
      * @return the list of any objects matching the given search condition (in the given page)
      */
     <T extends Any<?>> List<T> search(
-            Set<String> adminRealms, SearchCond searchCondition, int page, int itemsPerPage,
-            List<OrderByClause> orderBy, AnyTypeKind kind);
+            Realm base,
+            boolean recursive,
+            Set<String> adminRealms,
+            SearchCond searchCondition,
+            int page,
+            int itemsPerPage,
+            List<OrderByClause> orderBy,
+            AnyTypeKind kind);
 }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
index eb8d631152..3a4191d766 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
@@ -633,12 +633,19 @@ public class PGJPAJSONAnySearchDAO extends JPAAnySearchDAO {
     }
 
     @Override
-    protected int doCount(final Set<String> adminRealms, final SearchCond cond, final AnyTypeKind kind) {
+    protected int doCount(
+            final Realm base,
+            final boolean recursive,
+            final Set<String> adminRealms,
+            final SearchCond cond,
+            final AnyTypeKind kind) {
+
         List<Object> parameters = new ArrayList<>();
 
         SearchSupport svs = buildSearchSupport(kind);
 
-        Triple<String, Set<String>, Set<String>> filter = getAdminRealmsFilter(adminRealms, svs, parameters);
+        Triple<String, Set<String>, Set<String>> filter =
+                getAdminRealmsFilter(base, recursive, adminRealms, svs, parameters);
 
         Pair<StringBuilder, Set<String>> queryInfo =
                 getQuery(buildEffectiveCond(cond, filter.getMiddle(), filter.getRight(), kind), parameters, svs);
@@ -657,6 +664,8 @@ public class PGJPAJSONAnySearchDAO extends JPAAnySearchDAO {
     @Override
     @SuppressWarnings("unchecked")
     protected <T extends Any<?>> List<T> doSearch(
+            final Realm base,
+            final boolean recursive,
             final Set<String> adminRealms,
             final SearchCond cond,
             final int page,
@@ -669,7 +678,8 @@ public class PGJPAJSONAnySearchDAO extends JPAAnySearchDAO {
 
             SearchSupport svs = buildSearchSupport(kind);
 
-            Triple<String, Set<String>, Set<String>> filter = getAdminRealmsFilter(adminRealms, svs, parameters);
+            Triple<String, Set<String>, Set<String>> filter =
+                    getAdminRealmsFilter(base, recursive, adminRealms, svs, parameters);
 
             SearchCond effectiveCond = buildEffectiveCond(cond, filter.getMiddle(), filter.getRight(), kind);
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
index f4bd37af97..b6679d56ba 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
@@ -62,6 +62,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
+import org.springframework.util.CollectionUtils;
 
 public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO {
 
@@ -146,11 +147,18 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
         this.anyUtilsFactory = anyUtilsFactory;
     }
 
-    protected abstract int doCount(Set<String> adminRealms, SearchCond cond, AnyTypeKind kind);
+    protected abstract int doCount(
+            Realm base, boolean recursive, Set<String> adminRealms, SearchCond cond, AnyTypeKind kind);
 
     @Override
-    public int count(final Set<String> adminRealms, final SearchCond cond, final AnyTypeKind kind) {
-        if (adminRealms == null || adminRealms.isEmpty()) {
+    public int count(
+            final Realm base,
+            final boolean recursive,
+            final Set<String> adminRealms,
+            final SearchCond cond,
+            final AnyTypeKind kind) {
+
+        if (CollectionUtils.isEmpty(adminRealms)) {
             LOG.error("No realms provided");
             return 0;
         }
@@ -161,7 +169,7 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
             return 0;
         }
 
-        return doCount(adminRealms, cond, kind);
+        return doCount(base, recursive, adminRealms, cond, kind);
     }
 
     @Override
@@ -173,10 +181,12 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
     public <T extends Any<?>> List<T> search(
             final SearchCond cond, final List<OrderByClause> orderBy, final AnyTypeKind kind) {
 
-        return search(SyncopeConstants.FULL_ADMIN_REALMS, cond, -1, -1, orderBy, kind);
+        return search(realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, cond, -1, -1, orderBy, kind);
     }
 
     protected abstract <T extends Any<?>> List<T> doSearch(
+            Realm base,
+            boolean recursive,
             Set<String> adminRealms,
             SearchCond searchCondition,
             int page,
@@ -347,6 +357,8 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
 
     @Override
     public <T extends Any<?>> List<T> search(
+            final Realm base,
+            final boolean recursive,
             final Set<String> adminRealms,
             final SearchCond cond,
             final int page,
@@ -354,7 +366,7 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
             final List<OrderByClause> orderBy,
             final AnyTypeKind kind) {
 
-        if (adminRealms == null || adminRealms.isEmpty()) {
+        if (CollectionUtils.isEmpty(adminRealms)) {
             LOG.error("No realms provided");
             return List.of();
         }
@@ -377,6 +389,6 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
                     collect(Collectors.toList());
         }
 
-        return doSearch(adminRealms, cond, page, itemsPerPage, effectiveOrderBy, kind);
+        return doSearch(base, recursive, adminRealms, cond, page, itemsPerPage, effectiveOrderBy, kind);
     }
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index 4e2ed07453..08374986bf 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -100,6 +100,8 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected Triple<String, Set<String>, Set<String>> getAdminRealmsFilter(
+            final Realm base,
+            final boolean recursive,
             final Set<String> adminRealms,
             final SearchSupport svs,
             final List<Object> parameters) {
@@ -108,31 +110,37 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
         Set<String> dynRealmKeys = new HashSet<>();
         Set<String> groupOwners = new HashSet<>();
 
-        adminRealms.forEach(realmPath -> {
-            Optional<Pair<String, String>> goRealm = RealmUtils.parseGroupOwnerRealm(realmPath);
-            if (goRealm.isPresent()) {
-                groupOwners.add(goRealm.get().getRight());
-            } else if (realmPath.startsWith("/")) {
-                Realm realm = realmDAO.findByFullPath(realmPath);
-                if (realm == null) {
-                    SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
-                    noRealm.getElements().add("Invalid realm specified: " + realmPath);
-                    throw noRealm;
-                } else {
-                    realmKeys.addAll(realmDAO.findDescendants(realm).stream().
-                            map(Realm::getKey).collect(Collectors.toSet()));
-                }
-            } else {
-                DynRealm dynRealm = dynRealmDAO.find(realmPath);
-                if (dynRealm == null) {
-                    LOG.warn("Ignoring invalid dynamic realm {}", realmPath);
+        if (recursive) {
+            adminRealms.forEach(realmPath -> {
+                Optional<Pair<String, String>> goRealm = RealmUtils.parseGroupOwnerRealm(realmPath);
+                if (goRealm.isPresent()) {
+                    groupOwners.add(goRealm.get().getRight());
+                } else if (realmPath.startsWith("/")) {
+                    Realm realm = realmDAO.findByFullPath(realmPath);
+                    if (realm == null) {
+                        SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
+                        noRealm.getElements().add("Invalid realm specified: " + realmPath);
+                        throw noRealm;
+                    } else {
+                        realmKeys.addAll(realmDAO.findDescendants(realm).stream().
+                                map(Realm::getKey).collect(Collectors.toSet()));
+                    }
                 } else {
-                    dynRealmKeys.add(dynRealm.getKey());
+                    DynRealm dynRealm = dynRealmDAO.find(realmPath);
+                    if (dynRealm == null) {
+                        LOG.warn("Ignoring invalid dynamic realm {}", realmPath);
+                    } else {
+                        dynRealmKeys.add(dynRealm.getKey());
+                    }
                 }
+            });
+            if (!dynRealmKeys.isEmpty()) {
+                realmKeys.clear();
+            }
+        } else {
+            if (adminRealms.stream().anyMatch(r -> base.getFullPath().startsWith(r))) {
+                realmKeys.add(base.getKey());
             }
-        });
-        if (!dynRealmKeys.isEmpty()) {
-            realmKeys.clear();
         }
 
         return Triple.of(buildAdminRealmsFilter(realmKeys, svs, parameters), dynRealmKeys, groupOwners);
@@ -143,12 +151,19 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     @Override
-    protected int doCount(final Set<String> adminRealms, final SearchCond cond, final AnyTypeKind kind) {
+    protected int doCount(
+            final Realm base,
+            final boolean recursive,
+            final Set<String> adminRealms,
+            final SearchCond cond,
+            final AnyTypeKind kind) {
+
         List<Object> parameters = new ArrayList<>();
 
         SearchSupport svs = buildSearchSupport(kind);
 
-        Triple<String, Set<String>, Set<String>> filter = getAdminRealmsFilter(adminRealms, svs, parameters);
+        Triple<String, Set<String>, Set<String>> filter =
+                getAdminRealmsFilter(base, recursive, adminRealms, svs, parameters);
 
         // 1. get the query string from the search condition
         Pair<StringBuilder, Set<String>> queryInfo =
@@ -156,7 +171,7 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
 
         StringBuilder queryString = queryInfo.getLeft();
 
-        // 2. take into account administrative realms
+        // 2. take realms into account
         queryString.insert(0, "SELECT u.any_id FROM (");
         queryString.append(") u WHERE ").append(filter.getLeft());
 
@@ -173,6 +188,8 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     @Override
     @SuppressWarnings("unchecked")
     protected <T extends Any<?>> List<T> doSearch(
+            final Realm base,
+            final boolean recursive,
             final Set<String> adminRealms,
             final SearchCond cond,
             final int page,
@@ -185,7 +202,8 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
 
             SearchSupport svs = buildSearchSupport(kind);
 
-            Triple<String, Set<String>, Set<String>> filter = getAdminRealmsFilter(adminRealms, svs, parameters);
+            Triple<String, Set<String>, Set<String>> filter =
+                    getAdminRealmsFilter(base, recursive, adminRealms, svs, parameters);
 
             // 1. get the query string from the search condition
             Pair<StringBuilder, Set<String>> queryInfo =
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
index df071e5a70..45c13aa958 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
@@ -297,9 +297,12 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
         clearUDynMembers(merged);
         if (merged.getUDynMembership() != null) {
             SearchCond cond = buildDynMembershipCond(merged.getUDynMembership().getFIQLCond(), merged.getRealm());
-            int count = anySearchDAO.count(Set.of(merged.getRealm().getFullPath()), cond, AnyTypeKind.USER);
+            int count = anySearchDAO.count(
+                    merged.getRealm(), true, Set.of(merged.getRealm().getFullPath()), cond, AnyTypeKind.USER);
             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
                 List<User> matching = anySearchDAO.search(
+                        merged.getRealm(),
+                        true,
                         Set.of(merged.getRealm().getFullPath()),
                         cond,
                         page,
@@ -321,9 +324,12 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
         clearADynMembers(merged);
         merged.getADynMemberships().forEach(memb -> {
             SearchCond cond = buildDynMembershipCond(memb.getFIQLCond(), merged.getRealm());
-            int count = anySearchDAO.count(Set.of(merged.getRealm().getFullPath()), cond, AnyTypeKind.ANY_OBJECT);
+            int count = anySearchDAO.count(
+                    merged.getRealm(), true, Set.of(merged.getRealm().getFullPath()), cond, AnyTypeKind.ANY_OBJECT);
             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
                 List<AnyObject> matching = anySearchDAO.search(
+                        merged.getRealm(),
+                        true,
                         Set.of(merged.getRealm().getFullPath()),
                         cond,
                         page,
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
index 11df808b1e..e4dad5d2f4 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
@@ -274,16 +274,17 @@ public class AnySearchTest extends AbstractTest {
 
         assertTrue(cond.isValid());
 
-        int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
+        int count = searchDAO.count(
+                realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
         assertEquals(1, count);
 
-        List<User> users = searchDAO.search(SyncopeConstants.FULL_ADMIN_REALMS,
-                cond, 1, 2, List.of(), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(
+                realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, cond, 1, 2, List.of(), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
 
-        users = searchDAO.search(SyncopeConstants.FULL_ADMIN_REALMS,
-                cond, 2, 2, List.of(), AnyTypeKind.USER);
+        users = searchDAO.search(
+                realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, cond, 2, 2, List.of(), AnyTypeKind.USER);
         assertNotNull(users);
         assertTrue(users.isEmpty());
     }
@@ -315,6 +316,12 @@ public class AnySearchTest extends AbstractTest {
         assertTrue(matchingStar.stream().anyMatch(user -> "verdi".equals(user.getUsername())));
         assertTrue(matchingStar.stream().anyMatch(user -> "rossini".equals(user.getUsername())));
         assertEquals(union, matchingStar.stream().map(User::getUsername).collect(Collectors.toSet()));
+
+        matchingStar = searchDAO.search(realmDAO.getRoot(), false, SyncopeConstants.FULL_ADMIN_REALMS,
+                SearchCond.getLeaf(groupCond), -1, -1, List.of(), AnyTypeKind.USER);
+        assertNotNull(matchingStar);
+        assertTrue(matchingStar.stream().anyMatch(user -> "verdi".equals(user.getUsername())));
+        assertTrue(matchingStar.stream().noneMatch(user -> "rossini".equals(user.getUsername())));
     }
 
     @Test
@@ -439,8 +446,7 @@ public class AnySearchTest extends AbstractTest {
                 SearchCond.getLeaf(usernameLeafCond),
                 SearchCond.getLeaf(idRightCond));
 
-        List<User> matchingUsers = searchDAO.search(
-                searchCondition, AnyTypeKind.USER);
+        List<User> matchingUsers = searchDAO.search(searchCondition, AnyTypeKind.USER);
         assertNotNull(matchingUsers);
         assertEquals(2, matchingUsers.size());
     }
@@ -459,8 +465,7 @@ public class AnySearchTest extends AbstractTest {
                 SearchCond.getLeaf(usernameLeafCond),
                 SearchCond.getLeaf(idRightCond));
 
-        List<User> matchingUsers = searchDAO.search(
-                searchCondition, AnyTypeKind.USER);
+        List<User> matchingUsers = searchDAO.search(searchCondition, AnyTypeKind.USER);
         assertNotNull(matchingUsers);
         assertEquals(2, matchingUsers.size());
     }
@@ -578,7 +583,9 @@ public class AnySearchTest extends AbstractTest {
 
         List<User> users = searchDAO.search(searchCondition, orderByClauses, AnyTypeKind.USER);
         assertEquals(
-                searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCondition, AnyTypeKind.USER),
+                searchDAO.count(
+                        realmDAO.getRoot(), true,
+                        SyncopeConstants.FULL_ADMIN_REALMS, searchCondition, AnyTypeKind.USER),
                 users.size());
     }
 
@@ -596,7 +603,9 @@ public class AnySearchTest extends AbstractTest {
         List<Group> groups = searchDAO.search(
                 searchCondition, List.of(orderByClause), AnyTypeKind.GROUP);
         assertEquals(
-                searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCondition, AnyTypeKind.GROUP),
+                searchDAO.count(
+                        realmDAO.getRoot(), true,
+                        SyncopeConstants.FULL_ADMIN_REALMS, searchCondition, AnyTypeKind.GROUP),
                 groups.size());
     }
 
@@ -668,10 +677,20 @@ public class AnySearchTest extends AbstractTest {
                     AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_SEARCH),
                     SyncopeConstants.ROOT_REALM);
 
-            assertEquals(1, searchDAO.count(authRealms, groupDAO.getAllMatchingCond(), AnyTypeKind.GROUP));
+            assertEquals(
+                    1,
+                    searchDAO.count(
+                            realmDAO.getRoot(), true, authRealms, groupDAO.getAllMatchingCond(), AnyTypeKind.GROUP));
 
             List<Group> groups = searchDAO.search(
-                    authRealms, groupDAO.getAllMatchingCond(), 1, 10, List.of(), AnyTypeKind.GROUP);
+                    realmDAO.getRoot(),
+                    true,
+                    authRealms,
+                    groupDAO.getAllMatchingCond(),
+                    1,
+                    10,
+                    List.of(),
+                    AnyTypeKind.GROUP);
             assertEquals(1, groups.size());
             assertEquals("37d15e4c-cdc1-460b-a591-8505c8133806", groups.get(0).getKey());
         } finally {
@@ -737,8 +756,8 @@ public class AnySearchTest extends AbstractTest {
         SearchCond searchCond = SearchCond.getOr(
                 SearchCond.getLeaf(isNullCond), SearchCond.getLeaf(likeCond));
 
-        Integer count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.USER);
-        assertNotNull(count);
+        int count = searchDAO.count(
+                realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.USER);
         assertTrue(count > 0);
     }
 
@@ -752,16 +771,13 @@ public class AnySearchTest extends AbstractTest {
         genderCond.setSchema("gender");
         genderCond.setExpression("M");
 
-        SearchCond orCond =
-                SearchCond.getOr(SearchCond.getLeaf(rossiniCond),
-                        SearchCond.getLeaf(genderCond));
+        SearchCond orCond = SearchCond.getOr(SearchCond.getLeaf(rossiniCond), SearchCond.getLeaf(genderCond));
 
         AttrCond belliniCond = new AttrCond(AttrCond.Type.EQ);
         belliniCond.setSchema("surname");
         belliniCond.setExpression("Bellini");
 
-        SearchCond searchCond =
-                SearchCond.getAnd(orCond, SearchCond.getLeaf(belliniCond));
+        SearchCond searchCond = SearchCond.getAnd(orCond, SearchCond.getLeaf(belliniCond));
 
         List<User> users = searchDAO.search(searchCond, AnyTypeKind.USER);
         assertNotNull(users);
@@ -810,8 +826,7 @@ public class AnySearchTest extends AbstractTest {
         AnyTypeCond anyTypeCond = new AnyTypeCond();
         anyTypeCond.setAnyTypeKey(service.getKey());
 
-        searchCondition = SearchCond.getAnd(
-                SearchCond.getLeaf(groupCond), SearchCond.getLeaf(anyTypeCond));
+        searchCondition = SearchCond.getAnd(SearchCond.getLeaf(groupCond), SearchCond.getLeaf(anyTypeCond));
 
         matching = searchDAO.search(searchCondition, AnyTypeKind.ANY_OBJECT);
         assertEquals(1, matching.size());
@@ -834,6 +849,8 @@ public class AnySearchTest extends AbstractTest {
         orderByClauses.add(orderByClause);
 
         List<User> users = searchDAO.search(
+                realmDAO.getRoot(),
+                true,
                 SyncopeConstants.FULL_ADMIN_REALMS,
                 SearchCond.getLeaf(fullnameLeafCond),
                 -1,
@@ -851,8 +868,7 @@ public class AnySearchTest extends AbstractTest {
         AttrCond idRightCond = new AttrCond(AttrCond.Type.ISNOTNULL);
         idRightCond.setSchema("firstname");
 
-        SearchCond searchCondition = SearchCond.getAnd(
-                SearchCond.getLeaf(idLeftCond), SearchCond.getLeaf(idRightCond));
+        SearchCond searchCondition = SearchCond.getAnd(SearchCond.getLeaf(idLeftCond), SearchCond.getLeaf(idRightCond));
 
         List<OrderByClause> orderByClauses = new ArrayList<>();
         OrderByClause orderByClause = new OrderByClause();
@@ -861,8 +877,8 @@ public class AnySearchTest extends AbstractTest {
         orderByClauses.add(orderByClause);
 
         List<User> users = searchDAO.search(searchCondition, orderByClauses, AnyTypeKind.USER);
-        assertEquals(
-                searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCondition, AnyTypeKind.USER),
+        assertEquals(searchDAO.count(
+                realmDAO.getRoot(), true, SyncopeConstants.FULL_ADMIN_REALMS, searchCondition, AnyTypeKind.USER),
                 users.size());
 
         // search by attribute with unique constraint
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
index b371889e7f..051ba5588b 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
@@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -130,14 +131,16 @@ public class AnySearchTest extends AbstractTest {
         anyCond.setSchema("id");
 
         List<User> users = searchDAO.search(
-                Collections.singleton(SyncopeConstants.ROOT_REALM),
+                realmDAO.getRoot(), true,
+                Set.of(SyncopeConstants.ROOT_REALM),
                 SearchCond.getLeaf(anyCond), 1, 100, Collections.emptyList(), AnyTypeKind.USER);
         assertNotNull(users);
         assertTrue(users.stream().anyMatch(user -> rossini.getKey().equals(user.getKey())));
 
         // 3. search all users with director owner's entitlements: only rossini is returned
         users = searchDAO.search(
-                Collections.singleton(RealmUtils.getGroupOwnerRealm(group.getRealm().getFullPath(), group.getKey())),
+                group.getRealm(), true,
+                Set.of(RealmUtils.getGroupOwnerRealm(group.getRealm().getFullPath(), group.getKey())),
                 SearchCond.getLeaf(anyCond), 1, 100, Collections.emptyList(), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
index 55db10701f..29fb7347ca 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
@@ -37,6 +37,7 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
@@ -50,20 +51,23 @@ import org.xml.sax.helpers.AttributesImpl;
 public class GroupReportlet extends AbstractReportlet {
 
     @Autowired
-    private GroupDAO groupDAO;
+    protected RealmDAO realmDAO;
 
     @Autowired
-    private AnySearchDAO searchDAO;
+    protected GroupDAO groupDAO;
 
     @Autowired
-    private GroupDataBinder groupDataBinder;
+    protected AnySearchDAO searchDAO;
 
     @Autowired
-    private SearchCondVisitor searchCondVisitor;
+    protected GroupDataBinder groupDataBinder;
 
-    private GroupReportletConf conf;
+    @Autowired
+    protected SearchCondVisitor searchCondVisitor;
+
+    protected GroupReportletConf conf;
 
-    private static void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
+    protected void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
             throws SAXException {
 
         if (anyTO.getResources().isEmpty()) {
@@ -84,9 +88,12 @@ public class GroupReportlet extends AbstractReportlet {
         }
     }
 
-    private static void doExtractAttributes(final ContentHandler handler, final AnyTO anyTO,
-                                            final Collection<String> attrs, final Collection<String> derAttrs,
-                                            final Collection<String> virAttrs)
+    protected void doExtractAttributes(
+            final ContentHandler handler,
+            final AnyTO anyTO,
+            final Collection<String> attrs,
+            final Collection<String> derAttrs,
+            final Collection<String> virAttrs)
             throws SAXException {
 
         AttributesImpl atts = new AttributesImpl();
@@ -169,7 +176,7 @@ public class GroupReportlet extends AbstractReportlet {
         }
     }
 
-    private void doExtract(final ContentHandler handler, final List<Group> groups) throws SAXException {
+    protected void doExtract(final ContentHandler handler, final List<Group> groups) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         for (Group group : groups) {
             atts.clear();
@@ -241,7 +248,7 @@ public class GroupReportlet extends AbstractReportlet {
         }
     }
 
-    private void doExtractConf(final ContentHandler handler) throws SAXException {
+    protected void doExtractConf(final ContentHandler handler) throws SAXException {
         if (conf == null) {
             LOG.debug("Report configuration is not present");
         }
@@ -284,11 +291,15 @@ public class GroupReportlet extends AbstractReportlet {
         handler.endElement("", "", "configurations");
     }
 
-    private int count() {
+    protected int count() {
         return StringUtils.isBlank(conf.getMatchingCond())
                 ? groupDAO.count()
-                : searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS,
-                        SearchCondConverter.convert(searchCondVisitor, conf.getMatchingCond()), AnyTypeKind.GROUP);
+                : searchDAO.count(
+                        realmDAO.getRoot(),
+                        true,
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        SearchCondConverter.convert(searchCondVisitor, conf.getMatchingCond()),
+                        AnyTypeKind.GROUP);
     }
 
     @Override
@@ -319,6 +330,8 @@ public class GroupReportlet extends AbstractReportlet {
                 groups = groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);
             } else {
                 groups = searchDAO.search(
+                        realmDAO.getRoot(),
+                        true,
                         SyncopeConstants.FULL_ADMIN_REALMS,
                         SearchCondConverter.convert(searchCondVisitor, this.conf.getMatchingCond()),
                         page,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
index cc386a5d39..026cfbf1de 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
@@ -40,6 +40,7 @@ import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 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.ReportletConfClass;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
@@ -76,6 +77,9 @@ public class ReconciliationReportlet extends AbstractReportlet {
 
     private static final int PAGE_SIZE = 10;
 
+    @Autowired
+    private RealmDAO realmDAO;
+
     @Autowired
     private UserDAO userDAO;
 
@@ -388,7 +392,12 @@ public class ReconciliationReportlet extends AbstractReportlet {
         } else {
             SearchCond cond = SearchCondConverter.convert(searchCondVisitor, this.conf.getUserMatchingCond());
 
-            int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
+            int total = searchDAO.count(
+                    realmDAO.getRoot(),
+                    true,
+                    SyncopeConstants.FULL_ADMIN_REALMS,
+                    cond,
+                    AnyTypeKind.USER);
             int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
 
             status.set("Processing " + total + " users in " + pages + " pages");
@@ -400,6 +409,8 @@ public class ReconciliationReportlet extends AbstractReportlet {
                 status.set("Processing " + total + " users: page " + page + " of " + pages);
 
                 doExtract(handler, searchDAO.search(
+                        realmDAO.getRoot(),
+                        true,
                         SyncopeConstants.FULL_ADMIN_REALMS,
                         cond,
                         page,
@@ -428,7 +439,12 @@ public class ReconciliationReportlet extends AbstractReportlet {
         } else {
             SearchCond cond = SearchCondConverter.convert(searchCondVisitor, this.conf.getUserMatchingCond());
 
-            int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
+            int total = searchDAO.count(
+                    realmDAO.getRoot(),
+                    true,
+                    SyncopeConstants.FULL_ADMIN_REALMS,
+                    cond,
+                    AnyTypeKind.GROUP);
             int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
 
             status.set("Processing " + total + " groups in " + pages + " pages");
@@ -440,6 +456,8 @@ public class ReconciliationReportlet extends AbstractReportlet {
                 status.set("Processing " + total + " groups: page " + page + " of " + pages);
 
                 doExtract(handler, searchDAO.search(
+                        realmDAO.getRoot(),
+                        true,
                         SyncopeConstants.FULL_ADMIN_REALMS,
                         cond,
                         page,
@@ -460,7 +478,12 @@ public class ReconciliationReportlet extends AbstractReportlet {
                                 SearchCond.getLeaf(anyTypeCond),
                                 SearchCondConverter.convert(searchCondVisitor, this.conf.getAnyObjectMatchingCond()));
 
-                int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
+                int total = searchDAO.count(
+                        realmDAO.getRoot(),
+                        true,
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        cond,
+                        AnyTypeKind.ANY_OBJECT);
                 int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
 
                 status.set("Processing " + total + " any objects " + anyType.getKey() + " in " + pages + " pages");
@@ -475,6 +498,8 @@ public class ReconciliationReportlet extends AbstractReportlet {
                             + ": page " + page + " of " + pages);
 
                     doExtract(handler, searchDAO.search(
+                            realmDAO.getRoot(),
+                            true,
                             SyncopeConstants.FULL_ADMIN_REALMS,
                             cond,
                             page,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
index 7b5c1cddf9..e4f24b5ac0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
@@ -40,6 +40,7 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.entity.user.URelationship;
@@ -56,26 +57,29 @@ import org.xml.sax.helpers.AttributesImpl;
 public class UserReportlet extends AbstractReportlet {
 
     @Autowired
-    private UserDAO userDAO;
+    protected RealmDAO realmDAO;
 
     @Autowired
-    private AnySearchDAO searchDAO;
+    protected UserDAO userDAO;
 
     @Autowired
-    private UserDataBinder userDataBinder;
+    protected AnySearchDAO searchDAO;
 
     @Autowired
-    private GroupDataBinder groupDataBinder;
+    protected UserDataBinder userDataBinder;
 
     @Autowired
-    private AnyObjectDataBinder anyObjectDataBinder;
+    protected GroupDataBinder groupDataBinder;
 
     @Autowired
-    private SearchCondVisitor searchCondVisitor;
+    protected AnyObjectDataBinder anyObjectDataBinder;
 
-    private UserReportletConf conf;
+    @Autowired
+    protected SearchCondVisitor searchCondVisitor;
+
+    protected UserReportletConf conf;
 
-    private static void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
+    protected void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
             throws SAXException {
 
         if (anyTO.getResources().isEmpty()) {
@@ -96,9 +100,12 @@ public class UserReportlet extends AbstractReportlet {
         }
     }
 
-    private static void doExtractAttributes(final ContentHandler handler, final AnyTO anyTO,
-                                            final Collection<String> attrs, final Collection<String> derAttrs,
-                                            final Collection<String> virAttrs) throws SAXException {
+    protected void doExtractAttributes(
+            final ContentHandler handler,
+            final AnyTO anyTO,
+            final Collection<String> attrs,
+            final Collection<String> derAttrs,
+            final Collection<String> virAttrs) throws SAXException {
 
         AttributesImpl atts = new AttributesImpl();
         if (!attrs.isEmpty()) {
@@ -180,7 +187,7 @@ public class UserReportlet extends AbstractReportlet {
         }
     }
 
-    private void doExtract(final ContentHandler handler, final List<User> users) throws SAXException {
+    protected void doExtract(final ContentHandler handler, final List<User> users) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         for (User user : users) {
             atts.clear();
@@ -308,7 +315,7 @@ public class UserReportlet extends AbstractReportlet {
         }
     }
 
-    private void doExtractConf(final ContentHandler handler) throws SAXException {
+    protected void doExtractConf(final ContentHandler handler) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         handler.startElement("", "", "configurations", null);
         handler.startElement("", "", "userAttributes", atts);
@@ -345,11 +352,15 @@ public class UserReportlet extends AbstractReportlet {
         handler.endElement("", "", "configurations");
     }
 
-    private int count() {
+    protected int count() {
         return StringUtils.isBlank(conf.getMatchingCond())
                 ? userDAO.count()
-                : searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS,
-                        SearchCondConverter.convert(searchCondVisitor, this.conf.getMatchingCond()), AnyTypeKind.USER);
+                : searchDAO.count(
+                        realmDAO.getRoot(),
+                        true,
+                        SyncopeConstants.FULL_ADMIN_REALMS,
+                        SearchCondConverter.convert(searchCondVisitor, conf.getMatchingCond()),
+                        AnyTypeKind.USER);
     }
 
     @Override
@@ -380,6 +391,8 @@ public class UserReportlet extends AbstractReportlet {
                 users = userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);
             } else {
                 users = searchDAO.search(
+                        realmDAO.getRoot(),
+                        true,
                         SyncopeConstants.FULL_ADMIN_REALMS,
                         SearchCondConverter.convert(searchCondVisitor, this.conf.getMatchingCond()),
                         page,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index 6d26d10f8c..c2d0931f63 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -558,9 +558,12 @@ public class DefaultRealmPullResultHandler
                         AnyCond keyCond = new AnyCond(AttrCond.Type.ISNOTNULL);
                         keyCond.setSchema("key");
                         SearchCond allMatchingCond = SearchCond.getLeaf(keyCond);
-                        int users = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.USER);
-                        int groups = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.GROUP);
-                        int anyObjects = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
+                        int users = searchDAO.count(
+                                realmDAO.getRoot(), true, adminRealms, allMatchingCond, AnyTypeKind.USER);
+                        int groups = searchDAO.count(
+                                realmDAO.getRoot(), true, adminRealms, allMatchingCond, AnyTypeKind.GROUP);
+                        int anyObjects = searchDAO.count(
+                                realmDAO.getRoot(), true, adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
 
                         if (users + groups + anyObjects > 0) {
                             SyncopeClientException containedAnys = SyncopeClientException.build(
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index 1b0c3a0dae..333c482381 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -246,11 +246,15 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
                     ? anyDAO.getAllMatchingCond()
                     : SearchCondConverter.convert(searchCondVisitor, filter);
             int count = searchDAO.count(
+                    profile.getTask().getSourceRealm(),
+                    true,
                     Set.of(profile.getTask().getSourceRealm().getFullPath()),
                     cond,
                     provision.getAnyType().getKind());
             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1 && !interrupt; page++) {
                 List<? extends Any<?>> anys = searchDAO.search(
+                        profile.getTask().getSourceRealm(),
+                        true,
                         Set.of(profile.getTask().getSourceRealm().getFullPath()),
                         cond,
                         page,
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
index 56d180abad..ad7dd141b3 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
@@ -124,40 +124,51 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected Triple<Optional<Query>, Set<String>, Set<String>> getAdminRealmsFilter(
-            final AnyTypeKind kind, final Set<String> adminRealms) {
+            final Realm base,
+            final boolean recursive,
+            final Set<String> adminRealms,
+            final AnyTypeKind kind) {
 
         Set<String> dynRealmKeys = new HashSet<>();
         Set<String> groupOwners = new HashSet<>();
         List<Query> queries = new ArrayList<>();
 
-        adminRealms.forEach(realmPath -> {
-            Optional<Pair<String, String>> goRealm = RealmUtils.parseGroupOwnerRealm(realmPath);
-            if (goRealm.isPresent()) {
-                groupOwners.add(goRealm.get().getRight());
-            } else if (realmPath.startsWith("/")) {
-                Realm realm = realmDAO.findByFullPath(realmPath);
-                if (realm == null) {
-                    SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
-                    noRealm.getElements().add("Invalid realm specified: " + realmPath);
-                    throw noRealm;
-                } else {
-                    realmDAO.findDescendants(realm).forEach(descendant -> queries.add(
-                            new Query.Builder().term(QueryBuilders.term().
-                                    field("realm").value(FieldValue.of(descendant.getFullPath())).build()).
-                                    build()));
-                }
-            } else {
-                DynRealm dynRealm = dynRealmDAO.find(realmPath);
-                if (dynRealm == null) {
-                    LOG.warn("Ignoring invalid dynamic realm {}", realmPath);
+        if (recursive) {
+            adminRealms.forEach(realmPath -> {
+                Optional<Pair<String, String>> goRealm = RealmUtils.parseGroupOwnerRealm(realmPath);
+                if (goRealm.isPresent()) {
+                    groupOwners.add(goRealm.get().getRight());
+                } else if (realmPath.startsWith("/")) {
+                    Realm realm = realmDAO.findByFullPath(realmPath);
+                    if (realm == null) {
+                        SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
+                        noRealm.getElements().add("Invalid realm specified: " + realmPath);
+                        throw noRealm;
+                    } else {
+                        realmDAO.findDescendants(realm).forEach(descendant -> queries.add(
+                                new Query.Builder().term(QueryBuilders.term().
+                                        field("realm").value(FieldValue.of(descendant.getFullPath())).build()).
+                                        build()));
+                    }
                 } else {
-                    dynRealmKeys.add(dynRealm.getKey());
-                    queries.add(new Query.Builder().term(QueryBuilders.term().
-                            field("dynRealm").value(FieldValue.of(dynRealm.getKey())).build()).
-                            build());
+                    DynRealm dynRealm = dynRealmDAO.find(realmPath);
+                    if (dynRealm == null) {
+                        LOG.warn("Ignoring invalid dynamic realm {}", realmPath);
+                    } else {
+                        dynRealmKeys.add(dynRealm.getKey());
+                        queries.add(new Query.Builder().term(QueryBuilders.term().
+                                field("dynRealm").value(FieldValue.of(dynRealm.getKey())).build()).
+                                build());
+                    }
                 }
+            });
+        } else {
+            if (adminRealms.stream().anyMatch(r -> base.getFullPath().startsWith(r))) {
+                queries.add(new Query.Builder().term(QueryBuilders.term().
+                        field("realm").value(FieldValue.of(base.getFullPath())).build()).
+                        build());
             }
-        });
+        }
 
         return Triple.of(
                 dynRealmKeys.isEmpty() && groupOwners.isEmpty()
@@ -168,15 +179,28 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected Query getQuery(
+            final Realm base,
+            final boolean recursive,
             final Set<String> adminRealms,
             final SearchCond cond,
             final AnyTypeKind kind) {
 
-        Triple<Optional<Query>, Set<String>, Set<String>> filter = getAdminRealmsFilter(kind, adminRealms);
         Query query;
         if (SyncopeConstants.FULL_ADMIN_REALMS.equals(adminRealms)) {
             query = getQuery(cond, kind);
+
+            if (!recursive) {
+                query = new Query.Builder().bool(
+                        QueryBuilders.bool().
+                                must(new Query.Builder().term(QueryBuilders.term().
+                                        field("realm").value(FieldValue.of(base.getFullPath())).build()).
+                                        build()).
+                                must(query).build()).
+                        build();
+            }
         } else {
+            Triple<Optional<Query>, Set<String>, Set<String>> filter =
+                    getAdminRealmsFilter(base, recursive, adminRealms, kind);
             query = getQuery(buildEffectiveCond(cond, filter.getMiddle(), filter.getRight(), kind), kind);
 
             if (filter.getLeft().isPresent()) {
@@ -192,10 +216,16 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     @Override
-    protected int doCount(final Set<String> adminRealms, final SearchCond cond, final AnyTypeKind kind) {
+    protected int doCount(
+            final Realm base,
+            final boolean recursive,
+            final Set<String> adminRealms,
+            final SearchCond cond,
+            final AnyTypeKind kind) {
+
         CountRequest request = new CountRequest.Builder().
                 index(ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), kind)).
-                query(getQuery(adminRealms, cond, kind)).
+                query(getQuery(base, recursive, adminRealms, cond, kind)).
                 build();
         try {
             return (int) client.count(request).count();
@@ -245,6 +275,8 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
 
     @Override
     protected <T extends Any<?>> List<T> doSearch(
+            final Realm base,
+            final boolean recursive,
             final Set<String> adminRealms,
             final SearchCond cond,
             final int page,
@@ -255,7 +287,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
         SearchRequest request = new SearchRequest.Builder().
                 index(ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), kind)).
                 searchType(SearchType.QueryThenFetch).
-                query(getQuery(adminRealms, cond, kind)).
+                query(getQuery(base, recursive, adminRealms, cond, kind)).
                 from(itemsPerPage * (page <= 0 ? 0 : page - 1)).
                 size(itemsPerPage < 0 ? elasticsearchUtils.getIndexMaxResultWindow() : itemsPerPage).
                 sort(sortBuilders(kind, orderBy)).
diff --git a/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java b/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
index 770cb766a1..e08aed8854 100644
--- a/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
+++ b/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
@@ -99,7 +99,7 @@ public class ElasticsearchAnySearchDAOTest {
         // 2. test
         Set<String> adminRealms = Set.of(SyncopeConstants.ROOT_REALM);
         Triple<Optional<Query>, Set<String>, Set<String>> filter =
-                searchDAO.getAdminRealmsFilter(AnyTypeKind.USER, adminRealms);
+                searchDAO.getAdminRealmsFilter(realmDAO.getRoot(), true, adminRealms, AnyTypeKind.USER);
 
         assertThat(
                 new Query.Builder().disMax(QueryBuilders.disMax().queries(
@@ -122,7 +122,7 @@ public class ElasticsearchAnySearchDAOTest {
         // 2. test
         Set<String> adminRealms = Set.of("dyn");
         Triple<Optional<Query>, Set<String>, Set<String>> filter =
-                searchDAO.getAdminRealmsFilter(AnyTypeKind.USER, adminRealms);
+                searchDAO.getAdminRealmsFilter(realmDAO.getRoot(), true, adminRealms, AnyTypeKind.USER);
         assertFalse(filter.getLeft().isPresent());
         assertEquals(Set.of("dyn"), filter.getMiddle());
         assertEquals(Set.of(), filter.getRight());
@@ -132,7 +132,7 @@ public class ElasticsearchAnySearchDAOTest {
     public void getAdminRealmsFilter_groupOwner() {
         Set<String> adminRealms = Set.of(RealmUtils.getGroupOwnerRealm("/any", "groupKey"));
         Triple<Optional<Query>, Set<String>, Set<String>> filter =
-                searchDAO.getAdminRealmsFilter(AnyTypeKind.USER, adminRealms);
+                searchDAO.getAdminRealmsFilter(realmDAO.getRoot(), true, adminRealms, AnyTypeKind.USER);
         assertFalse(filter.getLeft().isPresent());
         assertEquals(Set.of(), filter.getMiddle());
         assertEquals(Set.of("groupKey"), filter.getRight());
@@ -164,7 +164,8 @@ public class ElasticsearchAnySearchDAOTest {
             SearchRequest request = new SearchRequest.Builder().
                     index(ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), AnyTypeKind.USER)).
                     searchType(SearchType.QueryThenFetch).
-                    query(searchDAO.getQuery(adminRealms, SearchCond.getLeaf(anyCond), AnyTypeKind.USER)).
+                    query(searchDAO.getQuery(realmDAO.findByFullPath("/any"), true,
+                            adminRealms, SearchCond.getLeaf(anyCond), AnyTypeKind.USER)).
                     from(1).
                     size(10).
                     build();
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
index d755d046a9..65943b14c1 100644
--- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
@@ -635,8 +635,7 @@ public class SCIMDataBinder {
 
         if (output(attributes, excludedAttributes, "members")) {
             int count = userLogic.search(searchCond,
-                    1, 1, List.of(),
-                    SyncopeConstants.ROOT_REALM, false).getLeft();
+                    1, 1, List.of(), SyncopeConstants.ROOT_REALM, true, false).getLeft();
 
             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
                 List<UserTO> users = userLogic.search(
@@ -645,6 +644,7 @@ public class SCIMDataBinder {
                         AnyDAO.DEFAULT_PAGE_SIZE,
                         List.of(),
                         SyncopeConstants.ROOT_REALM,
+                        true,
                         false).
                         getRight();
                 users.forEach(userTO -> group.getMembers().add(new Member(
diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java
index 708c08220f..51e30dfeea 100644
--- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java
+++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java
@@ -181,6 +181,7 @@ abstract class AbstractService<R extends SCIMResource> {
                 itemsPerPage,
                 sort,
                 SyncopeConstants.ROOT_REALM,
+                true,
                 false);
 
         if (result.getLeft() > confManager.get().getGeneralConf().getFilterMaxResults()) {
diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java
index f75c3c0f93..37b038f004 100644
--- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java
+++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java
@@ -127,8 +127,7 @@ public class GroupServiceImpl extends AbstractService<SCIMGroup> implements Grou
         membCond.setGroup(id);
         SearchCond searchCond = SearchCond.getLeaf(membCond);
         int count = userLogic.search(searchCond,
-                1, 1, List.of(),
-                SyncopeConstants.ROOT_REALM, false).getLeft();
+                1, 1, List.of(), SyncopeConstants.ROOT_REALM, true, false).getLeft();
         for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
             beforeMembers.addAll(userLogic.search(
                     searchCond,
@@ -136,6 +135,7 @@ public class GroupServiceImpl extends AbstractService<SCIMGroup> implements Grou
                     AnyDAO.DEFAULT_PAGE_SIZE,
                     List.of(),
                     SyncopeConstants.ROOT_REALM,
+                    true,
                     false).
                     getRight().stream().map(EntityTO::getKey).collect(Collectors.toSet()));
         }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
index 6602e57523..6ccc7a7f68 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
@@ -162,6 +162,13 @@ public class SearchITCase extends AbstractITCase {
         assertTrue(matchingStar.getResult().stream().anyMatch(user -> "verdi".equals(user.getUsername())));
         assertTrue(matchingStar.getResult().stream().anyMatch(user -> "rossini".equals(user.getUsername())));
         assertEquals(union, matchingStar.getResult().stream().map(UserTO::getUsername).collect(Collectors.toSet()));
+
+        matchingStar = userService.search(
+                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).recursive(false).
+                        fiql(SyncopeClient.getUserSearchConditionBuilder().inGroups("*child").query()).
+                        build());
+        assertTrue(matchingStar.getResult().stream().anyMatch(user -> "verdi".equals(user.getUsername())));
+        assertTrue(matchingStar.getResult().stream().noneMatch(user -> "rossini".equals(user.getUsername())));
     }
 
     @Test
diff --git a/pom.xml b/pom.xml
index 734724d67c..f7f8903c44 100644
--- a/pom.xml
+++ b/pom.xml
@@ -510,10 +510,10 @@ under the License.
     <docker.mysql.version>8.0</docker.mysql.version>
     <docker.mariadb.version>10</docker.mariadb.version>
 
-    <jdbc.postgresql.version>42.3.4</jdbc.postgresql.version>
+    <jdbc.postgresql.version>42.3.5</jdbc.postgresql.version>
     <jdbc.mysql.version>8.0.28</jdbc.mysql.version>
     <jdbc.mariadb.version>2.7.5</jdbc.mariadb.version>
-    <jdbc.mssql.version>10.2.0.jre</jdbc.mssql.version>
+    <jdbc.mssql.version>10.2.1.jre</jdbc.mssql.version>
     <jdbc.oracle.version>21.5.0.0</jdbc.oracle.version>
 
     <conf.directory>${project.build.directory}/test-classes</conf.directory>