From fad88bae0655787d9030d4f313c0a0dfcf2e47bb Mon Sep 17 00:00:00 2001
From: Guy Paddock <guy@rosieapp.com>
Date: Fri, 27 Oct 2017 04:49:37 +0000
Subject: [PATCH] Sub-resource search filter support

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java |   53 ++++++
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java     |  330 +++++++++++++++++++++++++++++++---------
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java  |    7 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java       |   39 ++++
 4 files changed, 344 insertions(+), 85 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
index 85fd139..05b2720 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
@@ -15,6 +15,7 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.guava.common.base.Preconditions.checkNotNull;
 import static org.forgerock.http.routing.RoutingMode.EQUALS;
 import static org.forgerock.http.routing.RoutingMode.STARTS_WITH;
 import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
@@ -75,6 +76,7 @@
 
     private NamingStrategy namingStrategy;
     private boolean flattenSubtree;
+    private Filter baseSearchFilter;
 
     SubResourceCollection(final String resourceId) {
         super(resourceId);
@@ -94,6 +96,18 @@
     }
 
     /**
+     * Gets the base filter that always restricts what LDAP entries are accessible through this
+     * collection, before any filters are applied from the request itself.
+     *
+     * The default is {@code null} (no base filter restriction at all).
+     *
+     * @return  Either a search filter; or {@code null} if no base search filter has been defined.
+     */
+    public Filter getBaseSearchFilter() {
+        return baseSearchFilter;
+    }
+
+    /**
      * Indicates that the JSON resource ID must be provided by the user, and will be used for naming the associated LDAP
      * entry. More specifically, LDAP entry names will be derived by appending a single RDN to the collection's base DN
      * composed of the specified attribute type and LDAP value taken from the LDAP entry once attribute mapping has been
@@ -259,6 +273,42 @@
         return this;
     }
 
+    /**
+     * Sets the base filter that always restricts what LDAP entries are accessible through this
+     * collection, before any filters are applied from the request itself.
+     *
+     * The default is {@code null} (no base filter restriction at all).
+     *
+     * @param   filter
+     *          The filter which should be used to restrict which LDAP entries are returned.
+     * @return  A reference to this object.
+     */
+    public SubResourceCollection baseSearchFilter(final Filter filter) {
+        this.baseSearchFilter = filter;
+        return this;
+    }
+
+    /**
+     * Sets the base filter that always restricts what LDAP entries are accessible through this
+     * collection, before any filters are applied from the request itself.
+     *
+     * The default is {@code null} (no base filter restriction at all).
+     *
+     * @param   filter
+     *          The filter which should be used to restrict which LDAP entries are returned.
+     * @return  A reference to this object.
+     */
+    public SubResourceCollection baseSearchFilter(final String filter) {
+        if (filter == null) {
+            baseSearchFilter((Filter)null);
+        }
+        else {
+            baseSearchFilter(Filter.valueOf(filter));
+        }
+
+        return this;
+    }
+
     @Override
     Router addRoutes(final Router router) {
         router.addRoute(requestUriMatcher(EQUALS, urlTemplate), readOnly(new CollectionHandler()));
@@ -299,7 +349,8 @@
             dnTemplateString.isEmpty() ? null : glueObjectClasses,
             namingStrategy,
             resource,
-            this.flattenSubtree);
+            flattenSubtree,
+            baseSearchFilter);
     }
 
     private String idFrom(final Context context) {
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java
index 127ee64..4b65ea9 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java
@@ -140,10 +140,16 @@
     private final Resource resource;
     private final Attribute glueObjectClasses;
     private final boolean flattenSubtree;
+    private Filter baseSearchFilter;
+
+    SubResourceImpl(final Rest2Ldap rest2Ldap, final DN baseDn, final Attribute glueObjectClasses,
+                    final NamingStrategy namingStrategy, final Resource resource) {
+        this(rest2Ldap, baseDn, glueObjectClasses, namingStrategy, resource, false, null);
+    }
 
     SubResourceImpl(final Rest2Ldap rest2Ldap, final DN baseDn, final Attribute glueObjectClasses,
                     final NamingStrategy namingStrategy, final Resource resource,
-                    final boolean flattenSubtree) {
+                    final boolean flattenSubtree, final Filter baseSearchFilter) {
         this.readOnUpdatePolicy = rest2Ldap.getOptions().get(READ_ON_UPDATE_POLICY);
         this.useSubtreeDelete = rest2Ldap.getOptions().get(USE_SUBTREE_DELETE);
         this.usePermissiveModify = rest2Ldap.getOptions().get(USE_PERMISSIVE_MODIFY);
@@ -155,6 +161,7 @@
         this.namingStrategy = namingStrategy;
         this.resource = resource;
         this.flattenSubtree = flattenSubtree;
+        this.baseSearchFilter = baseSearchFilter;
     }
 
     Promise<ActionResponse, ResourceException> action(
@@ -506,9 +513,34 @@
     Promise<QueryResponse, ResourceException> query(
             final Context context, final QueryRequest request, final QueryResourceHandler resourceHandler) {
         return getLdapFilter(context, request.getQueryFilter())
+                .then(applyBaseSearchFilter())
                 .thenAsync(runQuery(context, request, resourceHandler));
     }
 
+    /**
+     * Generates a function that applies any base filter that this sub-resource may have been
+     * initialized with.
+     *
+     * @return  The function to invoke to apply a base filter, if one has been specified.
+     */
+    private Function<Filter, Filter, ResourceException> applyBaseSearchFilter() {
+        return new Function<Filter, Filter, ResourceException>() {
+            @Override
+            public Filter apply(final Filter requestFilter) throws ResourceException {
+                final Filter baseSearchFilter = SubResourceImpl.this.baseSearchFilter,
+                             searchFilter;
+
+                if (baseSearchFilter != null) {
+                    searchFilter = Filter.and(baseSearchFilter, requestFilter);
+                } else {
+                    searchFilter = requestFilter;
+                }
+
+                return searchFilter;
+            }
+        };
+    }
+
     // FIXME: supporting assertions against sub-type properties.
     private Promise<Filter, ResourceException> getLdapFilter(
             final Context context, final QueryFilter<JsonPointer> queryFilter) {
@@ -525,6 +557,7 @@
                     public Promise<Filter, ResourceException> visitAndFilter(
                             final Void unused, final List<QueryFilter<JsonPointer>> subFilters) {
                         final List<Promise<Filter, ResourceException>> promises = new ArrayList<>(subFilters.size());
+
                         for (final QueryFilter<JsonPointer> subFilter : subFilters) {
                             promises.add(subFilter.accept(this, unused));
                         }
@@ -534,14 +567,17 @@
                             public Filter apply(final List<Filter> value) {
                                 // Check for unmapped filter components and optimize.
                                 final Iterator<Filter> i = value.iterator();
+
                                 while (i.hasNext()) {
                                     final Filter f = i.next();
+
                                     if (f == alwaysFalse()) {
                                         return alwaysFalse();
                                     } else if (f == alwaysTrue()) {
                                         i.remove();
                                     }
                                 }
+
                                 switch (value.size()) {
                                 case 0:
                                     return alwaysTrue();
@@ -675,6 +711,7 @@
                                 parentDnAndType, resource, ROOT, field, STARTS_WITH, null, valueAssertion);
                     }
                 };
+        
         // Note that the returned LDAP filter may be null if it could not be mapped by any property mappers.
         return queryFilter.accept(visitor, null);
     }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
index 1e8bc86..92a1f33 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
@@ -140,12 +140,7 @@
 
     private SubResourceImpl singleton(final Context context) {
         return new SubResourceImpl(
-            rest2Ldap,
-            dnFrom(context),
-            null,
-            SINGLETON_NAMING_STRATEGY,
-            resource,
-            false);
+            rest2Ldap, dnFrom(context), null, SINGLETON_NAMING_STRATEGY, resource);
     }
 
     /**
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
index 208023f..cb1bab0 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
@@ -93,7 +93,7 @@
     private static final QueryFilter<JsonPointer> NO_FILTER = QueryFilter.alwaysTrue();
 
     @Test
-    public void testQueryAllWithNoSubtreeFlattening() throws Exception {
+    public void testQueryAllWithNoSubtreeFlatteningAndNoSearchFilter() throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new LinkedList<>();
         final QueryResponse result =
@@ -106,30 +106,75 @@
         assertThat(result.getPagedResultsCookie()).isNull();
         assertThat(result.getTotalPagedResults()).isEqualTo(-1);
 
-        assertThat(resources.get(0).getContent().get("_ou").isNotNull());
-        assertThat(resources.get(0).getContent().get("_ou").asString()).isEqualTo("level1");
+        checkThatOrgUnitsExist(resources, "level1");
 
-        assertThat(resources.get(1).getContent().get("_ou").isNull());
-        assertThat(resources.get(1).getId()).isEqualTo("test1");
-
-        assertThat(resources.get(2).getContent().get("_ou").isNull());
-        assertThat(resources.get(2).getId()).isEqualTo("test2");
-
-        assertThat(resources.get(3).getContent().get("_ou").isNull());
-        assertThat(resources.get(3).getId()).isEqualTo("test3");
-
-        assertThat(resources.get(4).getContent().get("_ou").isNull());
-        assertThat(resources.get(4).getId()).isEqualTo("test4");
-
-        assertThat(resources.get(5).getContent().get("_ou").isNull());
-        assertThat(resources.get(5).getId()).isEqualTo("test5");
-
-        assertThat(resources.get(6).getContent().get("_ou").isNull());
-        assertThat(resources.get(6).getId()).isEqualTo("test6");
+        checkThatUsersExist(resources, 1,
+          "test1",
+          "test2",
+          "test3",
+          "test4",
+          "test5",
+          "test6"
+        );
     }
 
     @Test
-    public void testQueryAllWithSubtreeFlattening() throws Exception {
+    public void testQueryAllWithSearchFilterAndNoSubtreeFlattening() throws Exception {
+        final Connection connection = newConnection();
+        final List<ResourceResponse> resources = new LinkedList<>();
+        final QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("top-level-users").setQueryFilter(NO_FILTER),
+                resources);
+
+        assertThat(resources).hasSize(6);
+        assertThat(result.getPagedResultsCookie()).isNull();
+        assertThat(result.getTotalPagedResults()).isEqualTo(-1);
+
+        checkThatUsersExist(resources,
+          "test1",
+          "test2",
+          "test3",
+          "test4",
+          "test5",
+          "test6"
+        );
+    }
+
+    @Test
+    public void testQueryAllWithSubtreeFlatteningAndNoSearchFilter() throws Exception {
+        final Connection connection = newConnection();
+        final List<ResourceResponse> resources = new LinkedList<>();
+        final QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("all-entries").setQueryFilter(NO_FILTER),
+                resources);
+
+        assertThat(resources).hasSize(10);
+        assertThat(result.getPagedResultsCookie()).isNull();
+        assertThat(result.getTotalPagedResults()).isEqualTo(-1);
+
+        checkThatOrgUnitsExist(resources,
+          "level1",
+          "level2"
+        );
+
+        checkThatUsersExist(resources, 2,
+          "sub2",
+          "sub1",
+          "test1",
+          "test2",
+          "test3",
+          "test4",
+          "test5",
+          "test6"
+        );
+    }
+
+    @Test
+    public void testQueryAllWithSubtreeFlatteningAndSearchFilter() throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new LinkedList<>();
         final QueryResponse result =
@@ -138,47 +183,31 @@
                 newQueryRequest("all-users").setQueryFilter(NO_FILTER),
                 resources);
 
-        assertThat(resources).hasSize(10);
+        assertThat(resources).hasSize(8);
         assertThat(result.getPagedResultsCookie()).isNull();
         assertThat(result.getTotalPagedResults()).isEqualTo(-1);
 
-        assertThat(resources.get(0).getContent().get("_ou").isNotNull());
-        assertThat(resources.get(0).getContent().get("_ou").asString()).isEqualTo("level1");
-
-        assertThat(resources.get(1).getContent().get("_ou").isNotNull());
-        assertThat(resources.get(1).getContent().get("_ou").asString()).isEqualTo("level2");
-
-        assertThat(resources.get(2).getContent().get("_ou").isNull());
-        assertThat(resources.get(2).getId()).isEqualTo("sub2");
-
-        assertThat(resources.get(3).getContent().get("_ou").isNull());
-        assertThat(resources.get(3).getId()).isEqualTo("sub1");
-
-        assertThat(resources.get(4).getContent().get("_ou").isNull());
-        assertThat(resources.get(4).getId()).isEqualTo("test1");
-
-        assertThat(resources.get(5).getContent().get("_ou").isNull());
-        assertThat(resources.get(5).getId()).isEqualTo("test2");
-
-        assertThat(resources.get(6).getContent().get("_ou").isNull());
-        assertThat(resources.get(6).getId()).isEqualTo("test3");
-
-        assertThat(resources.get(7).getContent().get("_ou").isNull());
-        assertThat(resources.get(7).getId()).isEqualTo("test4");
-
-        assertThat(resources.get(8).getContent().get("_ou").isNull());
-        assertThat(resources.get(8).getId()).isEqualTo("test5");
-
-        assertThat(resources.get(9).getContent().get("_ou").isNull());
-        assertThat(resources.get(9).getId()).isEqualTo("test6");
+        checkThatUsersExist(resources,
+          "sub2",
+          "sub1",
+          "test1",
+          "test2",
+          "test3",
+          "test4",
+          "test5",
+          "test6"
+        );
     }
 
     @Test
-    public void testQueryNoneWithNoSubtreeFlattening() throws Exception {
+    public void testQueryNoneWithNoSubtreeFlatteningAndNoSearchFilter() throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new LinkedList<>();
-        final QueryResponse result = connection.query(newAuthConnectionContext(),
-                newQueryRequest("").setQueryFilter(QueryFilter.<JsonPointer> alwaysFalse()), resources);
+        final QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("").setQueryFilter(QueryFilter.<JsonPointer> alwaysFalse()),
+                resources);
 
         assertThat(resources).hasSize(0);
         assertThat(result.getPagedResultsCookie()).isNull();
@@ -186,7 +215,39 @@
     }
 
     @Test
-    public void testQueryNoneWithSubtreeFlattening() throws Exception {
+    public void testQueryNoneWithSearchFilterAndNoSubtreeFlattening() throws Exception {
+        final Connection connection = newConnection();
+        final List<ResourceResponse> resources = new LinkedList<>();
+        final QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("top-level-users")
+                    .setQueryFilter(QueryFilter.<JsonPointer> alwaysFalse()),
+                resources);
+
+        assertThat(resources).hasSize(0);
+        assertThat(result.getPagedResultsCookie()).isNull();
+        assertThat(result.getTotalPagedResults()).isEqualTo(-1);
+    }
+
+    @Test
+    public void testQueryNoneWithSubtreeFlatteningAndNoSearchFilter() throws Exception {
+        final Connection connection = newConnection();
+        final List<ResourceResponse> resources = new LinkedList<>();
+        final QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("all-entries")
+                    .setQueryFilter(QueryFilter.<JsonPointer> alwaysFalse()),
+                resources);
+
+        assertThat(resources).hasSize(0);
+        assertThat(result.getPagedResultsCookie()).isNull();
+        assertThat(result.getTotalPagedResults()).isEqualTo(-1);
+    }
+
+    @Test
+    public void testQueryNoneWithSubtreeFlatteningAndSearchFilter() throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new LinkedList<>();
         final QueryResponse result = connection.query(newAuthConnectionContext(),
@@ -198,7 +259,8 @@
     }
 
     @Test
-    public void testQueryPageResultsCookie() throws Exception {
+    public void testQueryPageResultsCookieWithNoSubtreeFlatteningAndNoSearchFilter()
+    throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new ArrayList<>();
 
@@ -214,14 +276,11 @@
         assertThat(result.getPagedResultsCookie()).isNotNull();
         assertThat(resources).hasSize(3);
 
-        assertThat(resources.get(0).getContent().get("_ou").isNotNull());
-        assertThat(resources.get(0).getContent().get("_ou").asString()).isEqualTo("level1");
+        checkThatOrgUnitsExist(resources, "level1");
 
-        assertThat(resources.get(1).getContent().get("_ou").isNull());
-        assertThat(resources.get(1).getId()).isEqualTo("test1");
-
-        assertThat(resources.get(2).getContent().get("_ou").isNull());
-        assertThat(resources.get(2).getId()).isEqualTo("test2");
+        checkThatUsersExist(resources, 1,
+          "test1",
+          "test2");
 
         String cookie = result.getPagedResultsCookie();
 
@@ -240,14 +299,10 @@
         assertThat(result.getPagedResultsCookie()).isNotNull();
         assertThat(resources).hasSize(3);
 
-        assertThat(resources.get(0).getContent().get("_ou").isNull());
-        assertThat(resources.get(0).getId()).isEqualTo("test3");
-
-        assertThat(resources.get(1).getContent().get("_ou").isNull());
-        assertThat(resources.get(1).getId()).isEqualTo("test4");
-
-        assertThat(resources.get(2).getContent().get("_ou").isNull());
-        assertThat(resources.get(2).getId()).isEqualTo("test5");
+        checkThatUsersExist(resources,
+            "test3",
+            "test4",
+            "test5");
 
         cookie = result.getPagedResultsCookie();
 
@@ -266,12 +321,62 @@
         assertThat(result.getPagedResultsCookie()).isNull();
         assertThat(resources).hasSize(1);
 
-        assertThat(resources.get(0).getContent().get("_ou").isNull());
-        assertThat(resources.get(0).getId()).isEqualTo("test6");
+        checkThatUsersExist(resources,
+            "test6");
     }
 
     @Test
-    public void testQueryPageResultsIndexed() throws Exception {
+    public void testQueryPageResultsCookieWithSubtreeFlatteningAndSearchFilter() throws Exception {
+        final Connection connection = newConnection();
+        final List<ResourceResponse> resources = new ArrayList<>();
+
+        // Read first page.
+        QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("all-users")
+                    .setQueryFilter(NO_FILTER)
+                    .setPageSize(5),
+                resources);
+
+        assertThat(result.getPagedResultsCookie()).isNotNull();
+        assertThat(resources).hasSize(5);
+
+        checkThatUsersExist(resources,
+            "sub2",
+            "sub1",
+            "test1",
+            "test2",
+            "test3");
+
+        String cookie = result.getPagedResultsCookie();
+
+        resources.clear();
+
+        // Read second page.
+        result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("all-users")
+                    .setQueryFilter(NO_FILTER)
+                    .setPageSize(5)
+                    .setPagedResultsCookie(cookie),
+                resources);
+
+        assertThat(result.getPagedResultsCookie()).isNull();
+        assertThat(resources).hasSize(3);
+
+        checkThatUsersExist(resources,
+            "test4",
+            "test5",
+            "test6");
+
+        resources.clear();
+    }
+
+    @Test
+    public void testQueryPageResultsIndexedWithNoSubtreeFlatteningAndNoSearchFilter()
+    throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new ArrayList<>();
 
@@ -286,8 +391,33 @@
 
         assertThat(result.getPagedResultsCookie()).isNotNull();
         assertThat(resources).hasSize(2);
-        assertThat(resources.get(0).getId()).isEqualTo("test2");
-        assertThat(resources.get(1).getId()).isEqualTo("test3");
+
+        checkThatUsersExist(resources,
+            "test2",
+            "test3");
+    }
+
+    @Test
+    public void testQueryPageResultsIndexedWithSubtreeFlatteningAndSearchFilter() throws Exception {
+        final Connection connection = newConnection();
+        final List<ResourceResponse> resources = new ArrayList<>();
+
+        QueryResponse result =
+            connection.query(
+                newAuthConnectionContext(),
+                newQueryRequest("all-users")
+                    .setQueryFilter(NO_FILTER)
+                    .setPageSize(3)
+                    .setPagedResultsOffset(1),
+                resources);
+
+        assertThat(result.getPagedResultsCookie()).isNotNull();
+        assertThat(resources).hasSize(3);
+
+        checkThatUsersExist(resources,
+            "test2",
+            "test3",
+            "test4");
     }
 
     @Test(expectedExceptions = NotFoundException.class)
@@ -869,11 +999,25 @@
                         .useClientDnNaming("uid"))
                 .subResource(
                     collectionOf("user")
+                        .urlTemplate("top-level-users")
+                        .dnTemplate("dc=test")
+                        .useClientDnNaming("uid")
+                        .baseSearchFilter("(objectClass=person)"))
+                .subResource(
+                    collectionOf("user")
+                        .urlTemplate("all-entries")
+                        .dnTemplate("dc=test")
+                        .useClientDnNaming("uid")
+                        .isReadOnly(true)
+                        .flattenSubtree(true))
+                .subResource(
+                    collectionOf("user")
                         .urlTemplate("all-users")
                         .dnTemplate("dc=test")
                         .useClientDnNaming("uid")
                         .isReadOnly(true)
-                        .flattenSubtree(true)),
+                        .flattenSubtree(true)
+                        .baseSearchFilter("(objectClass=person)")),
             resource("user")
                 .objectClasses("top", "person")
                 .property(
@@ -915,6 +1059,38 @@
             .isEqualTo(expectedResource.getContent().getObject());
     }
 
+    private void checkThatOrgUnitsExist(final List<ResourceResponse> resources,
+                                        final String... orgUnitIds) {
+        checkThatOrgUnitsExist(resources, 0, orgUnitIds);
+    }
+
+    private void checkThatOrgUnitsExist(final List<ResourceResponse> resources,
+                                        final int startingIndex,
+                                        final String... expectedOrgUnitIds) {
+        for (int orgUnitIndex = 0; orgUnitIndex < expectedOrgUnitIds.length; ++orgUnitIndex) {
+            final ResourceResponse resource = resources.get(startingIndex + orgUnitIndex);
+            final JsonValue orgUnitId = resource.getContent().get("_ou");
+
+            assertThat(orgUnitId).isNotNull();
+            assertThat(orgUnitId.asString()).isEqualTo(expectedOrgUnitIds[orgUnitIndex]);
+        }
+    }
+
+    private void checkThatUsersExist(final List<ResourceResponse> resources,
+                                     final String... expectedUserIds) {
+        checkThatUsersExist(resources, 0, expectedUserIds);
+    }
+
+    private void checkThatUsersExist(final List<ResourceResponse> resources,
+                                     final int startingIndex, final String... expectedUserIds) {
+        for (int userIndex = 0; userIndex < expectedUserIds.length; ++userIndex) {
+            final ResourceResponse resource = resources.get(startingIndex + userIndex);
+
+            assertThat(resource.getContent().get("_ou").isNull());
+            assertThat(resource.getId()).isEqualTo(expectedUserIds[userIndex]);
+        }
+    }
+
     private AuthenticatedConnectionContext newAuthConnectionContext() throws IOException {
         return newAuthConnectionContext(new ArrayList<Request>());
     }

--
Gitblit v1.10.0