opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -20,11 +20,12 @@ import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.forgerock.opendj.ldap.Functions.fixedFunction; import static org.forgerock.opendj.rest2ldap.Utils.base64ToByteString; import static org.forgerock.opendj.rest2ldap.Utils.byteStringToBase64; import static org.forgerock.opendj.rest2ldap.Utils.byteStringToJson; import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute; import static org.forgerock.opendj.rest2ldap.Utils.jsonToByteString; import static org.forgerock.opendj.rest2ldap.Utils.toFilter; import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY; import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE; import java.util.Collection; @@ -55,7 +56,6 @@ private Collection<Object> defaultJSONValues = Collections.emptySet(); private ByteString defaultLDAPValue = null; private Function<Object, ByteString, Void> encoder = null; private boolean isIgnoreUpdates = true; private boolean isRequired = false; private boolean isSingleValued = false; private final AttributeDescription ldapAttributeName; @@ -119,45 +119,41 @@ } /** * Indicates whether or not an attempt to update the LDAP attribute should * be ignored when the update is incompatible with the writability policy. * The default is {@code true}. * Indicates that JSON values are base 64 encodings of binary data. Calling * this method is equivalent to the following: * * @param ignore * {@code true} an attempt to update the LDAP attribute should be * ignored. * <pre> * mapper.decoder(...); // function that converts binary data to base 64 * mapper.encoder(...); // function that converts base 64 to binary data * </pre> * * @return This attribute mapper. */ public SimpleAttributeMapper ignoreUpdates(final boolean ignore) { this.isIgnoreUpdates = ignore; public SimpleAttributeMapper isBinary() { decoder = byteStringToBase64(); encoder = base64ToByteString(); return this; } /** * Indicates that the LDAP attribute is mandatory and must be provided * during create requests. The default is {@code false}. * during create requests. * * @param isRequired * {@code true} if the LDAP attribute is mandatory and must be * provided during create requests. * @return This attribute mapper. */ public SimpleAttributeMapper required(final boolean isRequired) { this.isRequired = isRequired; public SimpleAttributeMapper isRequired() { this.isRequired = true; return this; } /** * Forces a multi-valued LDAP attribute to be represented as a single-valued * JSON value, rather than an array of values. The default is {@code false}. * Indicates that multi-valued LDAP attribute should be represented as a * single-valued JSON value, rather than an array of values. * * @param isSingleValued * {@code true} if the LDAP attribute should be treated as a * single-valued attribute. * @return This attribute mapper. */ public SimpleAttributeMapper singleValued(final boolean isSingleValued) { this.isSingleValued = isSingleValued; public SimpleAttributeMapper isSingleValued() { this.isSingleValued = true; return this; } @@ -209,7 +205,7 @@ try { final List<Attribute> result; if (v == null || v.isNull()) { if (isRequired()) { if (attributeIsRequired()) { // FIXME: improve error message. throw new BadRequestException("no value provided"); } else if (defaultLDAPValue != null) { @@ -219,11 +215,11 @@ } else { result = emptyList(); } } else if (v.isList() && isSingleValued()) { } else if (v.isList() && attributeIsSingleValued()) { // FIXME: improve error message. throw new BadRequestException("expected single value, but got multiple values"); } else if (isCreate()) { if (isIgnoreUpdates) { } else if (!writabilityPolicy.canCreate(ldapAttributeName)) { if (writabilityPolicy.discardWrites()) { result = emptyList(); } else { // FIXME: improve error message. @@ -253,16 +249,11 @@ } } private boolean isCreate() { return writabilityPolicy != READ_ONLY && ldapAttributeName.getAttributeType().isNoUserModification(); } private boolean isRequired() { private boolean attributeIsRequired() { return isRequired && defaultJSONValue == null; } private boolean isSingleValued() { private boolean attributeIsSingleValued() { return isSingleValued || ldapAttributeName.getAttributeType().isSingleValue(); } opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -91,6 +91,22 @@ } private static final Function<ByteString, String, Void> BYTESTRING_TO_BASE64 = new Function<ByteString, String, Void>() { @Override public String apply(ByteString value, Void p) { return value.toBase64String(); } }; private static final Function<Object, ByteString, Void> BASE64_TO_BYTESTRING = new Function<Object, ByteString, Void>() { @Override public ByteString apply(Object value, Void p) { return ByteString.valueOfBase64(String.valueOf(value)); } }; private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON = new Function<ByteString, Object, AttributeDescription>() { @Override @@ -144,6 +160,14 @@ return BYTESTRING_TO_JSON; } static Function<ByteString, String, Void> byteStringToBase64() { return BYTESTRING_TO_BASE64; } static Function<Object, ByteString, Void> base64ToByteString() { return BASE64_TO_BYTESTRING; } static <T> T ensureNotNull(final T object) { if (object == null) { throw new NullPointerException(); opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java
@@ -16,26 +16,64 @@ package org.forgerock.opendj.rest2ldap; import org.forgerock.opendj.ldap.AttributeDescription; /** * The writability policy determines whether or not an attribute supports * updates. */ public enum WritabilityPolicy { // @formatter:off /** * The attribute may be provided when creating a new resource, but cannot be * modified afterwards. * The attribute cannot be provided when creating a new resource, nor * modified afterwards. Attempts to update the attribute will result in an * error. */ CREATE_ONLY, READ_ONLY(false), /** * The attribute cannot be provided when creating a new resource, nor * modified afterwards. * modified afterwards. Attempts to update the attribute will not result in * an error (the new values will be ignored). */ READ_ONLY, READ_ONLY_DISCARD_WRITES(true), /** * The attribute may be provided when creating a new resource, but cannot be * modified afterwards. Attempts to update the attribute will result in an * error. */ CREATE_ONLY(false), /** * The attribute may be provided when creating a new resource, but cannot be * modified afterwards. Attempts to update the attribute will not result in * an error (the new values will be ignored). */ CREATE_ONLY_DISCARD_WRITES(true), /** * The attribute may be provided when creating a new resource, and modified * afterwards. */ READ_WRITE; READ_WRITE(false); // @formatter:on private final boolean discardWrites; private WritabilityPolicy(final boolean discardWrites) { this.discardWrites = discardWrites; } boolean canCreate(final AttributeDescription attribute) { return this != READ_ONLY && !attribute.getAttributeType().isNoUserModification(); } boolean canWrite(final AttributeDescription attribute) { return this == READ_WRITE && !attribute.getAttributeType().isNoUserModification(); } boolean discardWrites() { return discardWrites; } } opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -68,16 +68,16 @@ CollectionResourceProvider users = builder().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com") .attribute("schemas", constant(Arrays.asList("urn:scim:schemas:core:1.0"))) .attribute("id", simple("uid").singleValued(true).required(true).writability(CREATE_ONLY)) .attribute("rev", simple("etag").singleValued(true).writability(READ_ONLY)) .attribute("userName", simple("mail").singleValued(true).writability(READ_ONLY)) .attribute("displayName", simple("cn").singleValued(true).required(true)) .attribute("id", simple("uid").isSingleValued().isRequired().writability(CREATE_ONLY)) .attribute("rev", simple("etag").isSingleValued().writability(READ_ONLY)) .attribute("userName", simple("mail").isSingleValued().writability(READ_ONLY)) .attribute("displayName", simple("cn").isSingleValued().isRequired()) .attribute("name", object() .attribute("givenName", simple("givenName").singleValued(true)) .attribute("familyName", simple("sn").singleValued(true).required(true))) .attribute("givenName", simple("givenName").isSingleValued()) .attribute("familyName", simple("sn").isSingleValued().isRequired())) .attribute("contactInformation", object() .attribute("telephoneNumber", simple("telephoneNumber").singleValued(true)) .attribute("emailAddress", simple("mail").singleValued(true))) .attribute("telephoneNumber", simple("telephoneNumber").isSingleValued()) .attribute("emailAddress", simple("mail").isSingleValued())) .additionalLDAPAttribute("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson") .build(); router.addRoute("/users", users); @@ -85,7 +85,7 @@ // Create group resource. CollectionResourceProvider groups = builder().factory(ldapFactory).baseDN("ou=groups,dc=example,dc=com") .attribute("cn", simple("cn").singleValued(true)) .attribute("cn", simple("cn").isSingleValued()) .attribute("description", simple("description")) .attribute("member", simple("uniquemember")) .build();