mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
21.55.2013 2c6ef30e8b391d745eab7d11eebd98a950513be8
OPENDJ-757: Add Rest2LDAP gateway Servlet

* implement mapper configuration parsing
4 files modified
176 ■■■■ changed files
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 4 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java 4 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 162 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java 6 ●●●●● patch | view | raw | blame | history
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -17,7 +17,7 @@
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
@@ -223,7 +223,7 @@
                    handler.handleError(e);
                    return;
                }
                if (config.readOnUpdatePolicy() == USE_READ_ENTRY_CONTROLS) {
                if (config.readOnUpdatePolicy() == CONTROLS) {
                    final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
                    addRequest.addControl(PostReadRequestControl.newControl(false, attributes));
                }
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
@@ -35,7 +35,7 @@
     * state of the resource at the time the update was performed. This policy
     * requires that the LDAP server supports RFC 4527.
     */
    USE_READ_ENTRY_CONTROLS,
    CONTROLS,
    /**
     * The LDAP entry will be read non-atomically using an LDAP search when an
@@ -44,5 +44,5 @@
     * may not reflect the state of the resource at the time the update was
     * performed.
     */
    USE_SEARCH;
    SEARCH;
}
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -18,7 +18,7 @@
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import java.util.ArrayList;
@@ -29,6 +29,7 @@
import java.util.concurrent.TimeUnit;
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.json.fluent.JsonValueException;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.ResourceException;
@@ -66,8 +67,8 @@
        private ConnectionFactory factory;
        private MVCCStrategy mvccStrategy;
        private NameStrategy nameStrategy;
        private ReadOnUpdatePolicy readOnUpdatePolicy = USE_READ_ENTRY_CONTROLS;
        private final ObjectAttributeMapper rootMapper = new ObjectAttributeMapper();
        private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS;
        private AttributeMapper rootMapper;
        private Schema schema = Schema.getDefaultSchema();
        Builder() {
@@ -81,23 +82,7 @@
        }
        public Builder additionalLDAPAttribute(final String attribute, final Object... values) {
            additionalLDAPAttributes.add(new LinkedAttribute(attribute, values));
            return this;
        }
        /**
         * Creates a mapping for the named JSON attribute.
         *
         * @param name
         *            The name of the JSON attribute to be mapped.
         * @param mapper
         *            The attribute mapper responsible for mapping the JSON
         *            attribute to LDAP attribute(s).
         * @return A reference to this builder.
         */
        public Builder attribute(final String name, final AttributeMapper mapper) {
            rootMapper.attribute(name, mapper);
            return this;
            return additionalLDAPAttribute(new LinkedAttribute(ad(attribute), values));
        }
        public Builder baseDN(final DN dn) {
@@ -107,15 +92,13 @@
        }
        public Builder baseDN(final String dn) {
            ensureNotNull(dn);
            this.baseDN = DN.valueOf(dn);
            return this;
            return baseDN(DN.valueOf(dn, schema));
        }
        public CollectionResourceProvider build() {
            ensureNotNull(factory);
            ensureNotNull(baseDN);
            if (rootMapper.isEmpty()) {
            if (rootMapper == null) {
                throw new IllegalStateException("No mappings provided");
            }
            return new LDAPCollectionResourceProvider(baseDN, rootMapper, factory, nameStrategy,
@@ -152,8 +135,8 @@
         *     "baseDN" : "ou=people,dc=example,dc=com",
         *
         *     // The mechanism which should be used for read resources during updates, must be
         *     // one of "disabled", "useReadEntryControls", or "useSearch".
         *     "readOnUpdatePolicy" : "useReadEntryControls",
         *     // one of "disabled", "controls", or "search".
         *     "readOnUpdatePolicy" : "controls",
         *
         *     // Additional LDAP attributes which should be included with entries during add (create) operations.
         *     "additionalLDAPAttributes" : [
@@ -188,18 +171,18 @@
         *     "etagAttribute" : "etag",
         *
         *     // The JSON to LDAP attribute mappings.
         *     "attributes" : [
         *     "attributes" : {
         *         "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
         *         "id"          : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
         *         "rev"         : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
         *         "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
         *         "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
         *         "name"        : { "object"   : [
         *         "name"        : { "object"   : {
         *             "givenName"  : { "simple"   : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
         *             "familyName" : { "simple"   : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } },
         *         ],
         *         },
         *         ...
         *     ]
         *     }
         * }
         * </pre>
         *
@@ -211,6 +194,43 @@
         */
        public Builder configureMapping(final JsonValue configuration)
                throws IllegalArgumentException {
            baseDN(configuration.get("baseDN").required().asString());
            final JsonValue readOnUpdatePolicy = configuration.get("readOnUpdatePolicy");
            if (!readOnUpdatePolicy.isNull()) {
                readOnUpdatePolicy(readOnUpdatePolicy.asEnum(ReadOnUpdatePolicy.class));
            }
            for (final JsonValue v : configuration.get("additionalLDAPAttributes")) {
                final String type = v.get("type").required().asString();
                final List<Object> values = v.get("values").required().asList();
                additionalLDAPAttribute(new LinkedAttribute(type, values));
            }
            final JsonValue namingStrategy = configuration.get("namingStrategy");
            if (!namingStrategy.isNull()) {
                final String name = namingStrategy.get("strategy").required().asString();
                if (name.equalsIgnoreCase("clientDNNaming")) {
                    useClientDNNaming(namingStrategy.get("dnAttribute").required().asString());
                } else if (name.equalsIgnoreCase("clientNaming")) {
                    useClientNaming(namingStrategy.get("dnAttribute").required().asString(),
                            namingStrategy.get("idAttribute").required().asString());
                } else if (name.equalsIgnoreCase("serverNaming")) {
                    useServerNaming(namingStrategy.get("dnAttribute").required().asString(),
                            namingStrategy.get("idAttribute").required().asString());
                } else {
                    throw new IllegalArgumentException(
                            "Illegal naming strategy. Must be one of: clientDNNaming, clientNaming, or serverNaming");
                }
            }
            final JsonValue etagAttribute = configuration.get("etagAttribute");
            if (!etagAttribute.isNull()) {
                useEtagAttribute(etagAttribute.asString());
            }
            mapper(configureObjectMapper(configuration.get("attributes").required()));
            return this;
        }
@@ -220,6 +240,11 @@
            return this;
        }
        public Builder mapper(final AttributeMapper mapper) {
            this.rootMapper = mapper;
            return this;
        }
        /**
         * Sets the policy which should be used in order to read an entry before
         * it is deleted, or after it is added or modified.
@@ -254,7 +279,7 @@
        }
        public Builder useClientDNNaming(final String attribute) {
            return useClientDNNaming(Schema.getDefaultSchema().getAttributeType(attribute));
            return useClientDNNaming(at(attribute));
        }
        public Builder useClientNaming(final AttributeType dnAttribute,
@@ -264,8 +289,7 @@
        }
        public Builder useClientNaming(final String dnAttribute, final String idAttribute) {
            return useClientNaming(Schema.getDefaultSchema().getAttributeType(dnAttribute),
                    AttributeDescription.valueOf(idAttribute));
            return useClientNaming(at(dnAttribute), ad(idAttribute));
        }
        public Builder useEtagAttribute() {
@@ -278,7 +302,7 @@
        }
        public Builder useEtagAttribute(final String attribute) {
            return useEtagAttribute(AttributeDescription.valueOf(attribute));
            return useEtagAttribute(ad(attribute));
        }
        public Builder useServerEntryUUIDNaming(final AttributeType dnAttribute) {
@@ -287,7 +311,7 @@
        }
        public Builder useServerEntryUUIDNaming(final String dnAttribute) {
            return useServerEntryUUIDNaming(Schema.getDefaultSchema().getAttributeType(dnAttribute));
            return useServerEntryUUIDNaming(at(dnAttribute));
        }
        public Builder useServerNaming(final AttributeType dnAttribute,
@@ -297,8 +321,72 @@
        }
        public Builder useServerNaming(final String dnAttribute, final String idAttribute) {
            return useServerNaming(Schema.getDefaultSchema().getAttributeType(dnAttribute),
                    AttributeDescription.valueOf(idAttribute));
            return useServerNaming(at(dnAttribute), ad(idAttribute));
        }
        private AttributeDescription ad(final String attribute) {
            return AttributeDescription.valueOf(attribute, schema);
        }
        private AttributeType at(final String attribute) {
            return schema.getAttributeType(attribute);
        }
        private AttributeMapper configureMapper(final JsonValue mapper) {
            if (mapper.isDefined("constant")) {
                return constant(mapper.get("constant").getObject());
            } else if (mapper.isDefined("simple")) {
                final JsonValue config = mapper.get("simple");
                final SimpleAttributeMapper s =
                        simple(ad(config.get("ldapAttribute").required().asString()));
                if (config.isDefined("defaultJSONValue")) {
                    s.defaultJSONValue(config.get("defaultJSONValue").getObject());
                }
                if (config.isDefined("defaultLDAPValue")) {
                    s.defaultLDAPValue(config.get("defaultLDAPValue").getObject());
                }
                if (config.get("isBinary").defaultTo(false).asBoolean()) {
                    s.isBinary();
                }
                if (config.get("isRequired").defaultTo(false).asBoolean()) {
                    s.isRequired();
                }
                if (config.get("isSingleValued").defaultTo(false).asBoolean()) {
                    s.isSingleValued();
                }
                if (config.isDefined("writability")) {
                    final String writability = config.get("writability").asString();
                    if (writability.equalsIgnoreCase("readOnly")) {
                        s.writability(WritabilityPolicy.READ_ONLY);
                    } else if (writability.equalsIgnoreCase("readOnlyDiscardWrites")) {
                        s.writability(WritabilityPolicy.READ_ONLY_DISCARD_WRITES);
                    } else if (writability.equalsIgnoreCase("createOnly")) {
                        s.writability(WritabilityPolicy.CREATE_ONLY);
                    } else if (writability.equalsIgnoreCase("createOnlyDiscardWrites")) {
                        s.writability(WritabilityPolicy.CREATE_ONLY_DISCARD_WRITES);
                    } else if (writability.equalsIgnoreCase("readWrite")) {
                        s.writability(WritabilityPolicy.READ_WRITE);
                    } else {
                        throw new JsonValueException(mapper,
                                "Illegal writability: must be one of readOnly, readOnlyDiscardWrites, "
                                        + "createOnly, createOnlyDiscardWrites, or readWrite");
                    }
                }
                return s;
            } else if (mapper.isDefined("object")) {
                return configureObjectMapper(mapper.get("object"));
            } else {
                throw new JsonValueException(mapper,
                        "Illegal mapping: must contain constant, simple, or object");
            }
        }
        private ObjectAttributeMapper configureObjectMapper(final JsonValue mapper) {
            final ObjectAttributeMapper object = object();
            for (final String attribute : mapper.keys()) {
                object.attribute(attribute, configureMapper(mapper.get(attribute)));
            }
            return object;
        }
    }
opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -67,6 +67,7 @@
        // Create user resource.
        CollectionResourceProvider users =
                builder().connectionFactory(ldapFactory).baseDN("ou=people,dc=example,dc=com")
                    .mapper(object()
                    .attribute("schemas", constant(Arrays.asList("urn:scim:schemas:core:1.0")))
                    .attribute("id", simple("uid").isSingleValued().isRequired().writability(CREATE_ONLY))
                    .attribute("rev", simple("etag").isSingleValued().writability(READ_ONLY))
@@ -77,7 +78,7 @@
                            .attribute("familyName", simple("sn").isSingleValued().isRequired()))
                    .attribute("contactInformation", object()
                            .attribute("telephoneNumber", simple("telephoneNumber").isSingleValued())
                            .attribute("emailAddress", simple("mail").isSingleValued()))
                                    .attribute("emailAddress", simple("mail").isSingleValued())))
                    .additionalLDAPAttribute("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson")
                    .build();
        router.addRoute("/users", users);
@@ -85,9 +86,10 @@
        // Create group resource.
        CollectionResourceProvider groups =
                builder().connectionFactory(ldapFactory).baseDN("ou=groups,dc=example,dc=com")
                    .mapper(object()
                    .attribute("cn", simple("cn").isSingleValued())
                    .attribute("description", simple("description"))
                    .attribute("member", simple("uniquemember"))
                            .attribute("member", simple("uniquemember")))
                    .build();
        router.addRoute("/groups", groups);