From 71a180997c65b0b5d23453e1985ecc6225bb4a42 Mon Sep 17 00:00:00 2001
From: Guy Paddock <guy@rosieapp.com>
Date: Fri, 27 Oct 2017 05:00:00 +0000
Subject: [PATCH] Extends configurator for search filter support

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java     |  101 +++++++++++++++---------
 opendj-server-legacy/resource/config/rest2ldap/endpoints/api/example-v1.json                     |    6 +
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java |  105 ++++++++++++++++++++++++-
 opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap/endpoints/api/example-v1.json |    6 +
 4 files changed, 169 insertions(+), 49 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 90de7e3..0bb2c83 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
@@ -35,7 +35,8 @@
                 },
                 // 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.
+                // from "ou=people,dc=example,dc=com" and working down. It filters out any other
+                // structural elements, including organizations, org units, etc.
                 "all-users": {
                     "type": "collection",
                     "dnTemplate": "ou=people,dc=example,dc=com",
@@ -45,7 +46,8 @@
                         "dnAttribute": "uid"
                     },
                     "isReadOnly": true,
-                    "flattenSubtree": true
+                    "flattenSubtree": true,
+                    "baseSearchFilter": "(objectClass=person)"
                 },
                 "groups": {
                     "type": "collection",
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
index 5f83434..93520e8 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
@@ -311,52 +311,77 @@
         final String dnTemplate = config.get("dnTemplate").defaultTo("").asString();
         final Boolean isReadOnly = config.get("isReadOnly").defaultTo(false).asBoolean();
         final String resourceId = config.get("resource").required().asString();
-        final Boolean flattenSubtree =
-            config.get("flattenSubtree").defaultTo(false).asBoolean();
 
         final SubResourceType subResourceType =
           config.get("type").required().as(enumConstant(SubResourceType.class));
 
         if (subResourceType == SubResourceType.COLLECTION) {
-            final String[] glueObjectClasses =
-                config.get("glueObjectClasses")
-                    .defaultTo(emptyList())
-                    .asList(String.class)
-                    .toArray(new String[0]);
+            return configureCollectionSubResource(
+                config, resourceId, urlTemplate, dnTemplate, isReadOnly);
+        } else {
+            return configureSingletonSubResource(
+                config, resourceId, urlTemplate, dnTemplate, isReadOnly);
+        }
+    }
 
-            final SubResourceCollection collection =
-                collectionOf(resourceId)
+    private static SubResource configureCollectionSubResource(final JsonValue config,
+                                                              final String resourceId,
+                                                              final String urlTemplate,
+                                                              final String dnTemplate,
+                                                              final Boolean isReadOnly) {
+        final String[] glueObjectClasses =
+            config.get("glueObjectClasses")
+                .defaultTo(emptyList())
+                .asList(String.class)
+                .toArray(new String[0]);
+
+        final Boolean flattenSubtree = config.get("flattenSubtree").defaultTo(false).asBoolean();
+        final String searchFilter = config.get("baseSearchFilter").asString();
+
+        final SubResourceCollection collection =
+            collectionOf(resourceId)
+                .urlTemplate(urlTemplate)
+                .dnTemplate(dnTemplate)
+                .isReadOnly(isReadOnly)
+                .glueObjectClasses(glueObjectClasses)
+                .flattenSubtree(flattenSubtree)
+                .baseSearchFilter(searchFilter);
+
+        configureCollectionNamingStrategy(config, collection);
+
+        return collection;
+    }
+
+    private static void configureCollectionNamingStrategy(final JsonValue config,
+                                                          final SubResourceCollection collection) {
+        final JsonValue namingStrategy = config.get("namingStrategy").required();
+        final NamingStrategyType namingStrategyType =
+            namingStrategy.get("type").required().as(enumConstant(NamingStrategyType.class));
+
+        switch (namingStrategyType) {
+        case CLIENTDNNAMING:
+            collection.useClientDnNaming(namingStrategy.get("dnAttribute").required().asString());
+            break;
+        case CLIENTNAMING:
+            collection.useClientNaming(namingStrategy.get("dnAttribute").required().asString(),
+                                       namingStrategy.get("idAttribute").required().asString());
+            break;
+        case SERVERNAMING:
+            collection.useServerNaming(namingStrategy.get("dnAttribute").required().asString(),
+                                       namingStrategy.get("idAttribute").required().asString());
+            break;
+        }
+    }
+
+    private static SubResource configureSingletonSubResource(final JsonValue config,
+                                                             final String resourceId,
+                                                             final String urlTemplate,
+                                                             final String dnTemplate,
+                                                             final Boolean isReadOnly) {
+        return singletonOf(resourceId)
                     .urlTemplate(urlTemplate)
                     .dnTemplate(dnTemplate)
-                    .isReadOnly(isReadOnly)
-                    .glueObjectClasses(glueObjectClasses)
-                    .flattenSubtree(flattenSubtree);
-
-            final JsonValue namingStrategy = config.get("namingStrategy").required();
-            final NamingStrategyType namingStrategyType =
-                namingStrategy.get("type").required().as(enumConstant(NamingStrategyType.class));
-
-            switch (namingStrategyType) {
-            case CLIENTDNNAMING:
-                collection.useClientDnNaming(namingStrategy.get("dnAttribute").required().asString());
-                break;
-            case CLIENTNAMING:
-                collection.useClientNaming(namingStrategy.get("dnAttribute").required().asString(),
-                                           namingStrategy.get("idAttribute").required().asString());
-                break;
-            case SERVERNAMING:
-                collection.useServerNaming(namingStrategy.get("dnAttribute").required().asString(),
-                                           namingStrategy.get("idAttribute").required().asString());
-                break;
-            }
-
-            return collection;
-        } else {
-            return singletonOf(resourceId)
-                        .urlTemplate(urlTemplate)
-                        .dnTemplate(dnTemplate)
-                        .isReadOnly(isReadOnly);
-        }
+                    .isReadOnly(isReadOnly);
     }
 
     private static PropertyMapper configurePropertyMapper(final JsonValue mapper, final String defaultLdapAttribute) {
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 2a74a2d..e4bda7b 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
@@ -41,6 +41,7 @@
 import org.forgerock.json.JsonValue;
 import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
+import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.services.context.Context;
 import org.forgerock.services.context.RootContext;
 import org.forgerock.testng.ForgeRockTestCase;
@@ -136,7 +137,7 @@
     }
 
     @DataProvider
-    public Object[][] invalidSubResourceConfigurations() {
+    public Object[][] invalidSubResourceSubtreeFlatteningConfigurations() {
         // @Checkstyle:off
         return new Object[][] {
                 {
@@ -187,6 +188,48 @@
                 {
                         false,
                         false,
+                        null,
+                        "{"
+                                + "'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'"
+                                                        + "}"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        false,
+                        false,
+                        "(objectClass=person)",
+                        "{"
+                                + "'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'"
+                                                        + "},"
+                                                        + "'baseSearchFilter': '(objectClass=person)'"
+                                                + "}"
+                                        + "}"
+                                + "}"
+                        + "}"
+                },
+                {
+                        false,
+                        false,
+                        null,
                         "{"
                                 + "'example-v1': {"
                                         + "'subResources': {"
@@ -207,6 +250,7 @@
                 {
                         true,
                         false,
+                        null,
                         "{"
                                 + "'example-v1': {"
                                         + "'subResources': {"
@@ -227,6 +271,7 @@
                 {
                         true,
                         false,
+                        null,
                         "{"
                                 + "'example-v1': {"
                                         + "'subResources': {"
@@ -248,6 +293,7 @@
                 {
                         false,
                         false,
+                        null,
                         "{"
                                 + "'example-v1': {"
                                         + "'subResources': {"
@@ -269,6 +315,7 @@
                 {
                         true,
                         true,
+                        null,
                         "{"
                                 + "'example-v1': {"
                                         + "'subResources': {"
@@ -291,8 +338,9 @@
         // @Checkstyle:on
     }
 
-    @Test(dataProvider = "invalidSubResourceConfigurations")
-    public void testInvalidSubResourceConfigurations(final String rawJson) throws Exception {
+    @Test(dataProvider = "invalidSubResourceSubtreeFlatteningConfigurations")
+    public void testInvalidSubResourceSubtreeFlatteningConfigurations(final String rawJson)
+    throws Exception {
         try {
             Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson));
 
@@ -304,9 +352,44 @@
         }
     }
 
+    @Test
+    public void testInvalidSubResourceSearchFilterConfigurations()
+    throws Exception {
+        final String rawJson =
+            "{"
+                    + "'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'"
+                                            + "},"
+                                            + "'baseSearchFilter': 'badFilter'"
+                                    + "}"
+                            + "}"
+                    + "}"
+            + "}";
+
+        try {
+            Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson));
+
+            fail("Expected an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException ex) {
+            assertThat(ex.getMessage())
+                .isEqualTo(
+                    "The provided search filter \"badFilter\" was missing an equal sign in the " +
+                    "suspected simple filter component between positions 0 and 9");
+        }
+    }
+
     @Test(dataProvider = "validSubResourceConfigurations")
-    public void testValidSubResourceConfigurations(final boolean expectingReadOnly,
-                                                   final boolean expectingSubtreeFlattened,
+    public void testValidSubResourceConfigurations(final boolean expectedReadOnly,
+                                                   final boolean expectedSubtreeFlattened,
+                                                   final String expectedSearchFilter,
                                                    final String rawJson) throws Exception {
         final List<org.forgerock.opendj.rest2ldap.Resource> resources =
             Rest2LdapJsonConfigurator.configureResources(parseJson(rawJson));
@@ -326,8 +409,16 @@
 
         allUsersSubResource = (SubResourceCollection)subResources.get("all-users");
 
-        assertThat(allUsersSubResource.isReadOnly()).isEqualTo(expectingReadOnly);
-        assertThat(allUsersSubResource.shouldFlattenSubtree()).isEqualTo(expectingSubtreeFlattened);
+        assertThat(allUsersSubResource.isReadOnly()).isEqualTo(expectedReadOnly);
+        assertThat(allUsersSubResource.shouldFlattenSubtree()).isEqualTo(expectedSubtreeFlattened);
+
+        if (expectedSearchFilter == null) {
+            assertThat(allUsersSubResource.getBaseSearchFilter()).isNull();
+        }
+        else {
+            assertThat(allUsersSubResource.getBaseSearchFilter().toString())
+                .isEqualTo(expectedSearchFilter);
+        }
     }
 
     private RequestHandler createRequestHandler(final File endpointsDir) throws IOException {
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 90de7e3..0bb2c83 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
@@ -35,7 +35,8 @@
                 },
                 // 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.
+                // from "ou=people,dc=example,dc=com" and working down. It filters out any other
+                // structural elements, including organizations, org units, etc.
                 "all-users": {
                     "type": "collection",
                     "dnTemplate": "ou=people,dc=example,dc=com",
@@ -45,7 +46,8 @@
                         "dnAttribute": "uid"
                     },
                     "isReadOnly": true,
-                    "flattenSubtree": true
+                    "flattenSubtree": true,
+                    "baseSearchFilter": "(objectClass=person)"
                 },
                 "groups": {
                     "type": "collection",

--
Gitblit v1.10.0