From a2a71e1777beb48f98e64811ca6cd43286b79be3 Mon Sep 17 00:00:00 2001
From: Guy Paddock <guy@rosieapp.com>
Date: Fri, 27 Oct 2017 04:49:24 +0000
Subject: [PATCH] Tests for sub-tree flattening config

---
 opendj-server-legacy/resource/config/rest2ldap/endpoints/api/example-v1.json                     |   14 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java         |   11 ++
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java |  204 ++++++++++++++++++++++++++++++++++++++++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java                   |   25 ++++
 opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap/endpoints/api/example-v1.json |   14 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java                      |   19 +++
 6 files changed, 280 insertions(+), 7 deletions(-)

diff --git a/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap/endpoints/api/example-v1.json b/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap/endpoints/api/example-v1.json
index b5a0b02..90de7e3 100644
--- a/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap/endpoints/api/example-v1.json
+++ b/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap/endpoints/api/example-v1.json
@@ -33,6 +33,20 @@
                     },
                     "isReadOnly": true
                 },
+                // This resource provides a read-only view of all users in the system, including
+                // users nested underneath entries like org units, organizations, etc., starting
+                // from "ou=people,dc=example,dc=com" and working down.
+                "all-users": {
+                    "type": "collection",
+                    "dnTemplate": "ou=people,dc=example,dc=com",
+                    "resource": "frapi:opendj:rest2ldap:user:1.0",
+                    "namingStrategy": {
+                        "type": "clientDnNaming",
+                        "dnAttribute": "uid"
+                    },
+                    "isReadOnly": true,
+                    "flattenSubtree": true
+                },
                 "groups": {
                     "type": "collection",
                     "dnTemplate": "ou=groups,dc=example,dc=com",
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
index 35825f6..e7964bd 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
@@ -34,6 +34,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -484,7 +486,7 @@
      * @return The unique service ID for this resource, given the specified writability.
      */
     String getServiceId(boolean isReadOnly) {
-        StringBuilder serviceId = new StringBuilder(this.getResourceId());
+        final StringBuilder serviceId = new StringBuilder(this.getResourceId());
 
         if (isReadOnly) {
             serviceId.append(":read-only");
@@ -495,6 +497,21 @@
         return serviceId.toString();
     }
 
+    /**
+     * Gets a map of the sub-resources under this resource, keyed by URL template.
+     *
+     * @return  The map of sub-resource URL templates to sub-resources.
+     */
+    Map<String, SubResource> getSubResourceMap() {
+        final Map<String, SubResource> result = new HashMap<>();
+
+        for (SubResource subResource : this.subResources) {
+            result.put(subResource.getUrlTemplate(), subResource);
+        }
+
+        return result;
+    }
+
     void build(final Rest2Ldap rest2Ldap) {
         // Prevent re-entrant calls.
         if (isBuilt) {
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java
index 0e61043..ee6a16a 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java
@@ -60,9 +60,10 @@
 
     String urlTemplate = "";
     String dnTemplateString = "";
-    boolean isReadOnly = false;
-    Rest2Ldap rest2Ldap;
-    Resource resource;
+
+    protected boolean isReadOnly = false;
+    protected Rest2Ldap rest2Ldap;
+    protected Resource resource;
 
     SubResource(final String resourceId) {
         this.resourceId = resourceId;
@@ -80,9 +81,27 @@
 
     @Override
     public final String toString() {
+        return getUrlTemplate();
+    }
+
+    /**
+     * Gets the URL template that must match for this sub-resource to apply to a given request.
+     *
+     * @return  The URL template for this sub-resource.
+     */
+    public String getUrlTemplate() {
         return urlTemplate;
     }
 
+    /**
+     * Gets whether or not this sub-resource has been configured for read-only access.
+     *
+     * @return  {@code true} if the sub-resource is read-only; {@code false} otherwise.
+     */
+    public boolean isReadOnly() {
+        return isReadOnly;
+    }
+
     final Resource getResource() {
         return resource;
     }
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 d66681a..85fd139 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
@@ -83,6 +83,17 @@
     }
 
     /**
+     * Gets whether or not this sub-resource should flatten sub-entries in results.
+     *
+     * @return  {@code true} if entries deep in the sub-tree are included in a flattened
+     *          collection view; {@code false} if only entries at the top level of the DN for this
+     *          sub-resource should be returned.
+     */
+    public boolean shouldFlattenSubtree() {
+        return flattenSubtree;
+    }
+
+    /**
      * 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
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
index 7c2e399..2a74a2d 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
@@ -29,6 +29,8 @@
 import java.nio.file.Paths;
 import java.util.Collections;
 
+import java.util.List;
+import java.util.Map;
 import org.forgerock.api.CrestApiProducer;
 import org.forgerock.api.models.ApiDescription;
 import org.forgerock.api.models.Items;
@@ -43,6 +45,7 @@
 import org.forgerock.services.context.RootContext;
 import org.forgerock.testng.ForgeRockTestCase;
 import org.forgerock.util.Options;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -66,9 +69,8 @@
 
     @Test
     public void testConfigureEndpointsWithApiDescription() throws Exception {
-        final DescribableRequestHandler handler =
-            createDescribableHandler(CONFIG_DIR.resolve("endpoints").toFile());
-
+        final File endpointsDir = CONFIG_DIR.resolve("endpoints").toFile();
+        final DescribableRequestHandler handler = createDescribableHandler(endpointsDir);
         final ApiDescription api = requestApi(handler, "api/users/bjensen");
 
         assertThat(api).isNotNull();
@@ -82,6 +84,7 @@
         assertThat(api.getPaths().getNames()).containsOnly(
             "/api/users",
             "/api/read-only-users",
+            "/api/all-users",
             "/api/groups");
 
         assertThat(api.getDefinitions().getNames()).containsOnly(
@@ -132,6 +135,201 @@
         }
     }
 
+    @DataProvider
+    public Object[][] invalidSubResourceConfigurations() {
+        // @Checkstyle:off
+        return new Object[][] {
+                {
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'writeable-collection': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'flattenSubtree': true"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'writeable-collection': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'isReadOnly': false,"
+                                                        + "'flattenSubtree': true"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                }
+        };
+        // @Checkstyle:on
+    }
+
+    @DataProvider
+    public Object[][] validSubResourceConfigurations() {
+        // @Checkstyle:off
+        return new Object[][] {
+                {
+                        false,
+                        false,
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'all-users': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'flattenSubtree': false"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        true,
+                        false,
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'all-users': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'isReadOnly': true"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        true,
+                        false,
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'all-users': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'isReadOnly': true,"
+                                                        + "'flattenSubtree': false"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        false,
+                        false,
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'all-users': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'isReadOnly': false,"
+                                                        + "'flattenSubtree': false"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        true,
+                        true,
+                        "{"
+                                + "'example-v1': {"
+                                        + "'subResources': {"
+                                                + "'all-users': {"
+                                                        + "'type': 'collection',"
+                                                        + "'dnTemplate': 'ou=people,dc=example,dc=com',"
+                                                        + "'resource': 'frapi:opendj:rest2ldap:user:1.0',"
+                                                        + "'namingStrategy': {"
+                                                            + "'type': 'clientDnNaming',"
+                                                            + "'dnAttribute': 'uid'"
+                                                        + "},"
+                                                        + "'isReadOnly': true,"
+                                                        + "'flattenSubtree': true"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                }
+        };
+        // @Checkstyle:on
+    }
+
+    @Test(dataProvider = "invalidSubResourceConfigurations")
+    public void testInvalidSubResourceConfigurations(final String rawJson) throws Exception {
+        try {
+            Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson));
+
+            fail("Expected an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException ex) {
+            assertThat(ex.getMessage())
+                .isEqualTo("Sub-resources must be read-only to support sub-tree flattening.");
+        }
+    }
+
+    @Test(dataProvider = "validSubResourceConfigurations")
+    public void testValidSubResourceConfigurations(final boolean expectingReadOnly,
+                                                   final boolean expectingSubtreeFlattened,
+                                                   final String rawJson) throws Exception {
+        final List<org.forgerock.opendj.rest2ldap.Resource> resources =
+            Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson));
+        final org.forgerock.opendj.rest2ldap.Resource firstResource;
+        final Map<String, SubResource> subResources;
+        final SubResourceCollection allUsersSubResource;
+
+        assertThat(resources.size()).isEqualTo(1);
+
+        firstResource = resources.get(0);
+
+        assertThat(firstResource.getResourceId()).isEqualTo("example-v1");
+
+        subResources = firstResource.getSubResourceMap();
+
+        assertThat(subResources.size()).isEqualTo(1);
+
+        allUsersSubResource = (SubResourceCollection)subResources.get("all-users");
+
+        assertThat(allUsersSubResource.isReadOnly()).isEqualTo(expectingReadOnly);
+        assertThat(allUsersSubResource.shouldFlattenSubtree()).isEqualTo(expectingSubtreeFlattened);
+    }
+
     private RequestHandler createRequestHandler(final File endpointsDir) throws IOException {
         return Rest2LdapJsonConfigurator.configureEndpoints(endpointsDir, Options.defaultOptions());
     }
diff --git a/opendj-server-legacy/resource/config/rest2ldap/endpoints/api/example-v1.json b/opendj-server-legacy/resource/config/rest2ldap/endpoints/api/example-v1.json
index b5a0b02..90de7e3 100644
--- a/opendj-server-legacy/resource/config/rest2ldap/endpoints/api/example-v1.json
+++ b/opendj-server-legacy/resource/config/rest2ldap/endpoints/api/example-v1.json
@@ -33,6 +33,20 @@
                     },
                     "isReadOnly": true
                 },
+                // This resource provides a read-only view of all users in the system, including
+                // users nested underneath entries like org units, organizations, etc., starting
+                // from "ou=people,dc=example,dc=com" and working down.
+                "all-users": {
+                    "type": "collection",
+                    "dnTemplate": "ou=people,dc=example,dc=com",
+                    "resource": "frapi:opendj:rest2ldap:user:1.0",
+                    "namingStrategy": {
+                        "type": "clientDnNaming",
+                        "dnAttribute": "uid"
+                    },
+                    "isReadOnly": true,
+                    "flattenSubtree": true
+                },
                 "groups": {
                     "type": "collection",
                     "dnTemplate": "ou=groups,dc=example,dc=com",

--
Gitblit v1.10.0