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

Gaetan Boismal
30.02.2016 32edddb11bcc204628fbdaddf3b0d0e86530f6f8
OPENDJ-2955 Use i18n in rest2ldap
10 files added
17 files modified
646 ■■■■ changed files
opendj-rest2ldap/pom.xml 23 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java 76 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java 16 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 28 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java 17 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java 23 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 38 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java 63 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java 14 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java 35 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java 6 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java 14 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CtsAccessTokenResolver.java 13 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java 6 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Rfc7662AccessTokenResolver.java 13 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java 14 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties 110 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_ca_ES.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_de.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_es.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_fr.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_ja.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_ko.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_pl.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_zh_CN.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_zh_TW.properties 15 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CtsAccessTokenResolverTestCase.java 2 ●●● patch | view | raw | blame | history
opendj-rest2ldap/pom.xml
@@ -60,6 +60,11 @@
        </dependency>
        <dependency>
            <groupId>org.forgerock.commons</groupId>
            <artifactId>i18n-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.forgerock.openig</groupId>
            <artifactId>openig-oauth2-resource-server-filter</artifactId>
            <version>${openig.version}</version>
@@ -81,6 +86,24 @@
    <build>
        <plugins>
            <plugin>
                <groupId>org.forgerock.commons</groupId>
                <artifactId>i18n-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate-messages</goal>
                        </goals>
                        <configuration>
                            <messageFiles>
                                <messageFile>org/forgerock/opendj/rest2ldap/rest2ldap.properties</messageFile>
                            </messageFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java
@@ -15,12 +15,14 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.forgerock.opendj.ldap.Attributes.emptyAttribute;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.isNullOrEmpty;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
import static org.forgerock.opendj.rest2ldap.Utils.newNotSupportedException;
import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
import java.util.ArrayList;
@@ -31,7 +33,6 @@
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.Attribute;
@@ -108,14 +109,12 @@
                public List<Attribute> apply(Attribute newLDAPAttribute) throws ResourceException {
                    if (!writabilityPolicy.canCreate(ldapAttributeName)) {
                        if (!newLDAPAttribute.isEmpty() && !writabilityPolicy.discardWrites()) {
                            throw new BadRequestException(i18n("The request cannot be processed because it attempts "
                                    + "to create the read-only field '%s'", path));
                            throw newBadRequestException(ERR_CREATION_READ_ONLY_FIELD.get(path));
                        }
                        return Collections.emptyList();
                    } else if (newLDAPAttribute.isEmpty()) {
                        if (isRequired) {
                            throw new BadRequestException(i18n("The request cannot be processed because it attempts "
                                    + "to remove the required field '%s'", path));
                            throw newBadRequestException(ERR_REMOVE_REQUIRED_FIELD.get("create", path));
                        }
                        return Collections.emptyList();
                    }
@@ -148,9 +147,7 @@
             * if it is configured to discard writes.
             */
            if (!writabilityPolicy.canWrite(ldapAttributeName)) {
                throw new BadRequestException(i18n(
                        "The request cannot be processed because it attempts to modify "
                                + "the read-only field '%s'", path));
                throw newBadRequestException(ERR_MODIFY_READ_ONLY_FIELD.get("patch", path));
            }
            switch (field.size()) {
@@ -164,16 +161,12 @@
                if (attributeIsSingleValued()) {
                    if (v.isList()) {
                        // Single-valued field violation.
                        throw new BadRequestException(i18n(
                                "The request cannot be processed because an array of values was "
                                        + "provided for the single valued field '%s'", path));
                        throw newBadRequestException(ERR_ARRAY_FOR_SINGLE_VALUED_FIELD.get(path));
                    }
                } else if (!v.isList() && !operation.isIncrement()
                        && !(v.isNull() && (operation.isReplace() || operation.isRemove()))) {
                    // Multi-valued field violation.
                    throw new BadRequestException(i18n(
                            "The request cannot be processed because an array of values was "
                                    + "not provided for the multi-valued field '%s'", path));
                    throw newBadRequestException(ERR_NO_ARRAY_FOR_MULTI_VALUED_FIELD.get(path));
                }
                break;
            case 1:
@@ -189,25 +182,16 @@
                if (fieldName.equals("-") && operation.isAdd()) {
                    // Append a single value.
                    if (attributeIsSingleValued()) {
                        throw new BadRequestException(i18n(
                                "The request cannot be processed because it attempts to append a "
                                        + "value to the single valued field '%s'", path));
                        throw newBadRequestException(ERR_PATCH_APPEND_IN_SINGLE_VALUED_FIELD.get(path));
                    } else if (v.isList()) {
                        throw new BadRequestException(i18n(
                                "The request cannot be processed because it attempts to "
                                        + "perform an indexed append of an array of values to "
                                        + "the multi-valued field '%s'", path.child(fieldName)));
                        throw newBadRequestException(
                                ERR_PATCH_INDEXED_APPEND_TO_MULTI_VALUED_FIELD.get(path.child(fieldName)));
                    }
                } else if (fieldName.matches("[0-9]+")) {
                    // Array index - not allowed.
                    throw new NotSupportedException(i18n(
                            "The request cannot be processed because it included "
                                    + "an indexed patch operation '%s' which is not supported "
                                    + "by this resource provider", path.child(fieldName)));
                    throw newNotSupportedException(ERR_PATCH_INDEXED_OPERATION.get(path.child(fieldName)));
                } else {
                    throw new BadRequestException(i18n(
                            "The request cannot be processed because it included "
                                    + "an unrecognized field '%s'", path.child(fieldName)));
                    throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(fieldName)));
                }
                break;
            default:
@@ -215,9 +199,7 @@
                 * The patch operation targets the child of a sub-field. This is
                 * not possible for a LDAP attribute mapper.
                 */
                throw new BadRequestException(i18n(
                        "The request cannot be processed because it included "
                                + "an unrecognized field '%s'", path.child(field.get(0))));
                throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(field.get(0))));
            }
            // Check that the values are compatible with the type of patch operation.
@@ -232,10 +214,7 @@
                modType =
                        attributeIsSingleValued() ? ModificationType.REPLACE : ModificationType.ADD;
                if (newValues.isEmpty()) {
                    throw new BadRequestException(i18n(
                            "The request cannot be processed because it included "
                                    + "an add patch operation but no value(s) for field '%s'", path
                                    .child(field.get(0))));
                    throw newBadRequestException(ERR_PATCH_ADD_NO_VALUE_FOR_FIELD.get(path.child(field.get(0))));
                }
            } else if (operation.isRemove()) {
                modType = ModificationType.DELETE;
@@ -244,10 +223,7 @@
            } else if (operation.isIncrement()) {
                modType = ModificationType.INCREMENT;
            } else {
                throw new NotSupportedException(i18n(
                        "The request cannot be processed because it included "
                                + "an unsupported type of patch operation '%s'", operation
                                .getOperation()));
                throw newNotSupportedException(ERR_PATCH_UNSUPPORTED_OPERATION.get(operation.getOperation()));
            }
            // Create the modification.
@@ -255,9 +231,7 @@
                // Deleting the attribute.
                if (isRequired) {
                    return Promises.<List<Modification>, ResourceException> newExceptionPromise(
                            new BadRequestException(i18n(
                                "The request cannot be processed because it attempts to remove the required field '%s'",
                                path)));
                            newBadRequestException(ERR_REMOVE_REQUIRED_FIELD.get("update", path)));
                } else {
                    return Promises.newResultPromise(
                        singletonList(new Modification(modType, emptyAttribute(ldapAttributeName))));
@@ -300,9 +274,7 @@
                            // No change.
                            return Collections.emptyList();
                        }
                        throw new BadRequestException(i18n(
                            "The request cannot be processed because it attempts to modify the read-only field '%s'",
                            path));
                        throw newBadRequestException(ERR_MODIFY_READ_ONLY_FIELD.get("update", path));
                    }
                    if (oldLDAPAttribute.isEmpty() && newLDAPAttribute.isEmpty()) {
@@ -314,9 +286,7 @@
                    } else if (newLDAPAttribute.isEmpty()) {
                        // The attribute is being deleted - this is not allowed if the attribute is required.
                        if (isRequired) {
                            throw new BadRequestException(i18n(
                                "The request cannot be processed because it attempts to remove the required field '%s'",
                                path));
                            throw newBadRequestException(ERR_REMOVE_REQUIRED_FIELD.get("update", path));
                        }
                        return singletonList(new Modification(ModificationType.REPLACE, newLDAPAttribute));
                    } else {
@@ -359,15 +329,11 @@
        if (attributeIsSingleValued()) {
            if (v != null && v.isList()) {
                // Single-valued field violation.
                throw new BadRequestException(i18n(
                        "The request cannot be processed because an array of values was "
                                + "provided for the single valued field '%s'", path));
                throw newBadRequestException(ERR_ARRAY_FOR_SINGLE_VALUED_FIELD.get(path));
            }
        } else if (v != null && !v.isList()) {
            // Multi-valued field violation.
            throw new BadRequestException(i18n(
                    "The request cannot be processed because an array of values was "
                            + "not provided for the multi-valued field '%s'", path));
            throw newBadRequestException(ERR_NO_ARRAY_FOR_MULTI_VALUED_FIELD.get(path));
        }
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
@@ -15,10 +15,11 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.isNullOrEmpty;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
@@ -28,7 +29,6 @@
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.Attribute;
@@ -58,8 +58,8 @@
    Promise<List<Attribute>, ResourceException> create(final Connection connection, final JsonPointer path,
            final JsonValue v) {
        if (!isNullOrEmpty(v) && !v.getObject().equals(value.getObject())) {
            return Promises.<List<Attribute>, ResourceException> newExceptionPromise(new BadRequestException(i18n(
                    "The request cannot be processed because it attempts to create the read-only field '%s'", path)));
            return Promises.<List<Attribute>, ResourceException> newExceptionPromise(
                    newBadRequestException(ERR_CREATION_READ_ONLY_FIELD.get(path)));
        } else {
            return Promises.newResultPromise(Collections.<Attribute> emptyList());
        }
@@ -112,8 +112,8 @@
    @Override
    Promise<List<Modification>, ResourceException> patch(final Connection connection, final JsonPointer path,
            final PatchOperation operation) {
        return Promises.<List<Modification>, ResourceException> newExceptionPromise(new BadRequestException(i18n(
                "The request cannot be processed because it attempts to patch the read-only field '%s'", path)));
        return Promises.<List<Modification>, ResourceException> newExceptionPromise(
                newBadRequestException(ERR_PATCH_READ_ONLY_FIELD.get(path)));
    }
    @Override
@@ -125,8 +125,8 @@
    Promise<List<Modification>, ResourceException> update(
            final Connection connection, final JsonPointer path, final Entry e, final JsonValue v) {
        if (!isNullOrEmpty(v) && !v.getObject().equals(value.getObject())) {
            return Promises.<List<Modification>, ResourceException> newExceptionPromise(new BadRequestException(i18n(
                    "The request cannot be processed because it attempts to modify the read-only field '%s'", path)));
            return Promises.<List<Modification>, ResourceException> newExceptionPromise(
                    newBadRequestException(ERR_MODIFY_READ_ONLY_FIELD.get("update", path)));
        } else {
            return Promises.newResultPromise(Collections.<Modification> emptyList());
        }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -15,6 +15,7 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static java.util.Arrays.asList;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
@@ -24,7 +25,8 @@
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
import static org.forgerock.opendj.rest2ldap.Utils.newNotSupportedException;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import java.nio.charset.StandardCharsets;
@@ -135,7 +137,7 @@
    public Promise<ActionResponse, ResourceException> actionCollection(
            final Context context, final ActionRequest request) {
        return Promises.<ActionResponse, ResourceException> newExceptionPromise(
                                                            new NotSupportedException("Not yet implemented"));
                                                            newNotSupportedException(ERR_NOT_YET_IMPLEMENTED.get()));
    }
    @Override
@@ -146,7 +148,7 @@
            return passwordModify(context, resourceId, request);
        }
        return Promises.<ActionResponse, ResourceException> newExceptionPromise(
                new NotSupportedException("The action '" + actionId + "' is not supported"));
                newNotSupportedException(ERR_ACTION_NOT_SUPPORTED.get(actionId)));
    }
    private Promise<ActionResponse, ResourceException> passwordModify(
@@ -154,12 +156,12 @@
        if (!context.containsContext(ClientContext.class)
                || !context.asContext(ClientContext.class).isSecure()) {
            return Promises.newExceptionPromise(ResourceException.newResourceException(
                    ResourceException.FORBIDDEN, "Password modify requires a secure connection."));
                    ResourceException.FORBIDDEN, ERR_PASSWORD_MODIFY_SECURE_CONNECTION.get().toString()));
        }
        if (!context.containsContext(SecurityContext.class)
                || context.asContext(SecurityContext.class).getAuthenticationId() == null) {
            return Promises.newExceptionPromise(ResourceException.newResourceException(
                    ResourceException.FORBIDDEN, "Password modify requires user to be authenticated."));
                    ResourceException.FORBIDDEN, ERR_PASSWORD_MODIFY_USER_AUTHENTICATED.get().toString()));
        }
        final JsonValue jsonContent = request.getContent();
@@ -169,8 +171,8 @@
            oldPassword = jsonContent.get("oldPassword").asString();
            newPassword = jsonContent.get("newPassword").asString();
        } catch (JsonValueException e) {
            return Promises.newExceptionPromise(
                    ResourceException.newResourceException(ResourceException.BAD_REQUEST, e.getLocalizedMessage(), e));
            final ResourceException ex = newBadRequestException(ERR_PASSWORD_MODIFY_REQUEST_IS_INVALID.get(), e);
            return Promises.newExceptionPromise(ex);
        }
        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
@@ -844,8 +846,7 @@
    private void ensureMVCCSupported() throws NotSupportedException {
        if (etagAttribute == null) {
            throw new NotSupportedException(
                    i18n("Multi-version concurrency control is not supported by this resource"));
            throw newNotSupportedException(ERR_MVCC_NOT_SUPPORTED.get());
        }
    }
@@ -854,13 +855,10 @@
            ensureMVCCSupported();
            final String actualRevision = entry.parseAttribute(etagAttribute).asString();
            if (actualRevision == null) {
                throw new PreconditionFailedException(i18n(
                        "The resource could not be accessed because it did not contain any "
                                + "version information, when the version '%s' was expected", expectedRevision));
                throw new PreconditionFailedException(ERR_MVCC_NO_VERSION_INFORMATION.get(expectedRevision).toString());
            } else if (!expectedRevision.equals(actualRevision)) {
                throw new PreconditionFailedException(i18n(
                        "The resource could not be accessed because the expected version '%s' "
                                + "does not match the current version '%s'", expectedRevision, actualRevision));
                throw new PreconditionFailedException(
                        ERR_MVCC_VERSIONS_MISMATCH.get(expectedRevision, actualRevision).toString());
            }
        }
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
@@ -15,10 +15,11 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.json.resource.PatchOperation.operation;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
import java.util.AbstractMap.SimpleImmutableEntry;
@@ -31,7 +32,6 @@
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.Attribute;
@@ -196,9 +196,7 @@
                final String fieldName = field.get(0);
                final Mapping mapping = getMapping(fieldName);
                if (mapping == null) {
                    throw new BadRequestException(i18n(
                            "The request cannot be processed because it included "
                                    + "an unrecognized field '%s'", path.child(fieldName)));
                    throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(fieldName)));
                }
                final PatchOperation subOperation =
                        operation(operation.getOperation(), field.relativePointer(), v);
@@ -314,16 +312,11 @@
            if (v.isMap()) {
                for (final String attribute : v.asMap().keySet()) {
                    if (missingMappings.remove(toLowerCase(attribute)) == null) {
                        throw new BadRequestException(i18n(
                                "The request cannot be processed because it included "
                                        + "an unrecognized field '%s'", path.child(attribute)));
                        throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(attribute)));
                    }
                }
            } else {
                throw new BadRequestException(i18n(
                        "The request cannot be processed because it included "
                                + "the field '%s' whose value is the wrong type: "
                                + "an object is expected", path));
                throw newBadRequestException(ERR_FIELD_WRONG_TYPE.get(path));
            }
        }
        return missingMappings;
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -15,11 +15,12 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
@@ -31,7 +32,6 @@
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
@@ -206,15 +206,12 @@
                    }
                    if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) {
                        promise.handleException(new BadRequestException(
                                i18n("The request cannot be processed because the reference field '%s' contains "
                                        + "a value which does not contain a primary key", path)));
                        promise.handleException(newBadRequestException(ERR_REFERENCE_FIELD_NO_PRIMARY_KEY.get(path)));
                    }
                    if (primaryKeyAttribute.size() > 1) {
                        promise.handleException(new BadRequestException(
                                i18n("The request cannot be processed because the reference field '%s' contains "
                                        + "a value which contains multiple primary keys", path)));
                        promise.handleException(
                                newBadRequestException(ERR_REFERENCE_FIELD_MULTIPLE_PRIMARY_KEYS.get(path)));
                    }
                    // Now search for the referenced entry in to get its DN.
@@ -237,15 +234,11 @@
                                      try {
                                          throw error;
                                      } catch (final EntryNotFoundException e) {
                                          re = new BadRequestException(i18n(
                                                  "The request cannot be processed because the resource "
                                                  + "'%s' referenced in field '%s' does not exist",
                                          re = newBadRequestException(ERR_REFERENCE_FIELD_DOES_NOT_EXIST.get(
                                                  primaryKeyValue.toString(), path));
                                      } catch (final MultipleEntriesFoundException e) {
                                          re = new BadRequestException(i18n(
                                                  "The request cannot be processed because the resource "
                                                  + "'%s' referenced in field '%s' is ambiguous",
                                                  primaryKeyValue.toString(), path));
                                          re = newBadRequestException(
                                                  ERR_REFERENCE_FIELD_AMBIGUOUS.get(primaryKeyValue.toString(), path));
                                      } catch (final LdapException e) {
                                          re = asResourceException(e);
                                      }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -15,6 +15,7 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static java.util.Arrays.asList;
import static org.forgerock.json.resource.ResourceException.newResourceException;
import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool;
@@ -26,6 +27,9 @@
import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
import static org.forgerock.opendj.rest2ldap.Utils.newLocalizedIllegalArgumentException;
import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException;
import static org.forgerock.util.time.Duration.*;
import java.io.IOException;
@@ -39,8 +43,6 @@
import java.util.concurrent.TimeUnit;
import org.forgerock.json.JsonValue;
import org.forgerock.json.JsonValueException;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.AssertionFailureException;
@@ -170,7 +172,7 @@
        public CollectionResourceProvider build() {
            ensureNotNull(baseDN);
            if (rootMapper == null) {
                throw new IllegalStateException("No mappings provided");
                throw new IllegalStateException(ERR_CONFIG_NO_MAPPINGS_PROVIDED.get().toString());
            }
            return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy, etagAttribute,
                    new Config(readOnUpdatePolicy, useSubtreeDelete, usePermissiveModify, schema),
@@ -215,8 +217,8 @@
                    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");
                    throw newLocalizedIllegalArgumentException(ERR_CONFIG_UNKNOWN_NAMING_CONFIGURATION.get(
                            namingStrategy.asString(), "clientDNNaming, clientNaming or serverNaming"));
                }
            }
@@ -586,8 +588,8 @@
            } else if (mapper.isDefined("object")) {
                return configureObjectMapper(mapper.get("object"));
            } else {
                throw new JsonValueException(mapper,
                        "Illegal mapping: must contain constant, simple, or object");
                throw newJsonValueException(mapper, ERR_CONFIG_NO_MAPPING_IN_CONFIGURATION.get(
                        "constant, simple, reference or object"));
            }
        }
@@ -613,9 +615,8 @@
                } else if (writability.equalsIgnoreCase("readWrite")) {
                    return WritabilityPolicy.READ_WRITE;
                } else {
                    throw new JsonValueException(mapper,
                            "Illegal writability: must be one of readOnly, readOnlyDiscardWrites, "
                                    + "createOnly, createOnlyDiscardWrites, or readWrite");
                    throw newJsonValueException(mapper, ERR_CONFIG_UNKNOWN_WRITABILITY.get(writability,
                                "readOnly, readOnlyDiscardWrites, createOnly, createOnlyDiscardWrites, or readWrite"));
                }
            } else {
                return WritabilityPolicy.READ_WRITE;
@@ -632,7 +633,7 @@
                final AttributeDescription idAttribute, final boolean isServerProvided) {
            this.dnAttribute = AttributeDescription.create(dnAttribute);
            if (this.dnAttribute.equals(idAttribute)) {
                throw new IllegalArgumentException("DN and ID attributes must be different");
                throw newLocalizedIllegalArgumentException(ERR_CONFIG_NAMING_STRATEGY_DN_AND_ID_NOT_DIFFERENT.get());
            }
            this.idAttribute = ensureNotNull(idAttribute);
            this.isServerProvided = isServerProvided;
@@ -659,8 +660,7 @@
                final Entry entry) throws ResourceException {
            if (isServerProvided) {
                if (resourceId != null) {
                    throw new BadRequestException("Resources cannot be created with a "
                            + "client provided resource ID");
                    throw newBadRequestException(ERR_CLIENT_PROVIDER_RESOURCE_ID_MISSING.get());
                }
            } else {
                entry.addAttribute(new LinkedAttribute(idAttribute, ByteString.valueOfUtf8(resourceId)));
@@ -703,8 +703,7 @@
            } else if (entry.getAttribute(attribute) != null) {
                entry.setName(baseDN.child(rdn(entry.parseAttribute(attribute).asString())));
            } else {
                throw new BadRequestException("Resources cannot be created without a "
                        + "client provided resource ID");
                throw newBadRequestException(ERR_CLIENT_PROVIDER_RESOURCE_ID_MISSING.get());
            }
        }
@@ -937,7 +936,7 @@
                                simple.get("bindPassword").required().asString().toCharArray());
                options.set(AUTHN_BIND_REQUEST, bindRequest);
            } else {
                throw new IllegalArgumentException("Only simple authentication is supported");
                throw newLocalizedIllegalArgumentException(ERR_CONFIG_INVALID_AUTHENTICATION.get());
            }
        }
@@ -995,7 +994,7 @@
                secondary = parseLDAPServers(secondaryLDAPServers, connectionPoolSize, options);
            }
        } else if (!secondaryLDAPServers.isNull()) {
            throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration");
            throw newLocalizedIllegalArgumentException(ERR_CONFIG_INVALID_SECONDARY_LDAP_SERVER.get());
        }
        // Create fail-over.
@@ -1010,10 +1009,7 @@
            final String name, final int depth) {
        // Protect against infinite recursion in the configuration.
        if (depth > 100) {
            throw new IllegalArgumentException(
                    "The LDAP server configuration '"
                            + name
                            + "' could not be parsed because of potential circular inheritance dependencies");
            throw newLocalizedIllegalArgumentException(ERR_CONFIG_SERVER_CIRCULAR_DEPENDENCIES.get(name));
        }
        final JsonValue current = configuration.get(name).required();
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java
@@ -16,17 +16,21 @@
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.http.util.Json.readJsonLenient;
import static org.forgerock.json.JsonValueFunctions.duration;
import static org.forgerock.json.JsonValueFunctions.enumConstant;
import static org.forgerock.json.JsonValueFunctions.setOf;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.configureConnectionFactory;
import static org.forgerock.opendj.rest2ldap.Utils.newLocalizedIllegalArgumentException;
import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException;
import static org.forgerock.opendj.rest2ldap.authz.AuthenticationStrategies.*;
import static org.forgerock.opendj.rest2ldap.authz.Authorizations.*;
import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.*;
import static org.forgerock.opendj.rest2ldap.authz.CredentialExtractors.*;
import static org.forgerock.util.Reject.checkNotNull;
import static org.forgerock.util.Utils.closeSilently;
import static org.forgerock.util.Utils.joinAsString;
import java.io.IOException;
import java.io.InputStream;
@@ -57,7 +61,6 @@
import org.forgerock.http.io.Buffer;
import org.forgerock.http.protocol.Headers;
import org.forgerock.json.JsonValue;
import org.forgerock.json.JsonValueException;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.Router;
import org.forgerock.json.resource.http.CrestHttp;
@@ -112,16 +115,39 @@
    /** Define the method which should be used to resolve an OAuth2 access token. */
    private enum OAuth2ResolverType {
        RFC7662,
        OPENAM,
        CTS,
        FILE
        RFC7662, OPENAM, CTS, FILE;
        private static String listValues() {
            final List<String> values = new ArrayList<>();
            for (final OAuth2ResolverType value : OAuth2ResolverType.values()) {
                values.add(value.name().toLowerCase());
            }
            return joinAsString(",", values);
        }
    }
    @VisibleForTesting
    enum Policy { OAUTH2, BASIC, ANONYMOUS }
    private enum BindStrategy { SIMPLE, SEARCH, SASL_PLAIN }
    private enum BindStrategy {
        SIMPLE("simple"),
        SEARCH("search"),
        SASL_PLAIN("sasl-plain");
        private final String jsonField;
        BindStrategy(final String jsonField) {
            this.jsonField = jsonField;
        }
        private static String listValues() {
            final List<String> values = new ArrayList<>();
            for (final BindStrategy mapping : BindStrategy.values()) {
                values.add(mapping.jsonField);
            }
            return joinAsString(",", values);
        }
    }
    /**
     * Default constructor called by the HTTP Framework which will use the default configuration file location.
@@ -154,8 +180,7 @@
                    CrestHttp.newHttpHandler(configureRest2Ldap(configuration)),
                    buildAuthorizationFilter(configuration.get("authorization").required()));
        } catch (final Exception e) {
            // TODO i18n, once supported in opendj-rest2ldap
            final String errorMsg = "Unable to start Rest2Ldap Http Application";
            final String errorMsg = ERR_FAIL_PARSE_CONFIGURATION.get(e.getLocalizedMessage()).toString();
            LOG.error(errorMsg, e);
            stop();
            throw new HttpApplicationException(errorMsg, e);
@@ -267,7 +292,9 @@
        case FILE:
            return newFileAccessTokenResolver(configuration.get("file").get("folderPath").required().asString());
        default:
            throw new JsonValueException(resolver, "is not a supported access token resolver");
            throw newJsonValueException(resolver,
                                        ERR_CONFIG_OAUTH2_UNSUPPORTED_ACCESS_TOKEN_RESOLVER.get(
                                                resolver.getObject(), OAuth2ResolverType.listValues()));
        }
    }
@@ -280,8 +307,8 @@
                                                 rfc7662.get("clientId").required().asString(),
                                                 rfc7662.get("clientSecret").required().asString());
        } catch (final URISyntaxException e) {
            throw new IllegalArgumentException("The token introspection endpoint '"
                    + introspectionEndPointURL + "' URL has an invalid syntax: " + e.getLocalizedMessage(), e);
            throw new IllegalArgumentException(ERR_CONIFG_OAUTH2_INVALID_INTROSPECT_URL.get(
                    introspectionEndPointURL, e.getLocalizedMessage()).toString(), e);
        }
    }
@@ -289,15 +316,14 @@
        try {
            final Duration expiration = expirationJson.as(duration());
            if (expiration.isZero() || expiration.isUnlimited()) {
                throw new JsonValueException(expirationJson, "The cache expiration duration cannot be "
                        + (expiration.isZero() ? "zero" : "unlimited."));
                throw newJsonValueException(expirationJson,
                                            expiration.isZero() ? ERR_CONIFG_OAUTH2_CACHE_ZERO_DURATION.get()
                                                                : ERR_CONIFG_OAUTH2_CACHE_UNLIMITED_DURATION.get());
            }
            return expiration;
        } catch (final Exception e) {
            throw new JsonValueException(expirationJson,
                      "Malformed duration value '" + expirationJson.toString() + "' for cache expiration. "
                    + "The duration syntax supports all human readable notations from day ('days'', 'day'', 'd'') "
                    + "to nanosecond ('nanoseconds', 'nanosecond', 'nanosec', 'nanos', 'nano', 'ns')");
            throw newJsonValueException(expirationJson,
                                        ERR_CONFIG_OAUTH2_CACHE_INVALID_DURATION.get(expirationJson.toString()));
        }
    }
@@ -381,7 +407,8 @@
        case SASL_PLAIN:
            return buildSASLBindStrategy(config);
        default:
            throw new IllegalArgumentException("Unsupported strategy '" + strategy + "'");
            throw newLocalizedIllegalArgumentException(ERR_CONFIG_UNSUPPORTED_BIND_STRATEGY.get(
                    strategy, BindStrategy.listValues()));
        }
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -15,13 +15,14 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
@@ -124,10 +125,8 @@
                return newResultPromise(toFilter(type, ldapAttributeName.toString(), va));
            } catch (final Exception e) {
                // Invalid assertion value - bad request.
                return newExceptionPromise((ResourceException) new BadRequestException(i18n(
                        "The request cannot be processed because it contained an "
                                + "illegal filter assertion value '%s' for field '%s'",
                        String.valueOf(valueAssertion), path), e));
                return newExceptionPromise((ResourceException) newBadRequestException(
                        ERR_ILLEGAL_FILTER_ASSERTION_VALUE.get(String.valueOf(valueAssertion), path), e));
            }
        } else {
            // This attribute mapper does not support partial filtering.
@@ -141,9 +140,8 @@
        try {
            return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, encoder()));
        } catch (final Exception ex) {
            return newExceptionPromise((ResourceException) new BadRequestException(i18n(
                    "The request cannot be processed because an error occurred while "
                            + "encoding the values for the field '%s': %s", path, ex.getMessage())));
            return newExceptionPromise((ResourceException) newBadRequestException(
                    ERR_ENCODING_VALUES_FOR_FIELD.get(path, ex.getMessage())));
        }
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -11,10 +11,11 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2012-2015 ForgeRock AS.
 * Copyright 2012-2016 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static javax.xml.bind.DatatypeConverter.parseDateTime;
import static javax.xml.bind.DatatypeConverter.printDateTime;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
@@ -32,7 +33,12 @@
import java.util.List;
import java.util.Locale;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.json.JsonValue;
import org.forgerock.json.JsonValueException;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
@@ -152,8 +158,7 @@
            }
            return a;
        } else {
            throw new IllegalArgumentException("Unrecognized type of JSON value: "
                    + value.getClass().getName());
            throw newLocalizedIllegalArgumentException(ERR_UNRECOGNIZED_JSON_VALUE.get(value.getClass().getName()));
        }
    }
@@ -169,8 +174,8 @@
                        return ByteString.valueOfObject(value);
                    }
                } else {
                    throw new IllegalArgumentException("Unrecognized type of JSON value: "
                            + value.getClass().getName());
                    throw newLocalizedIllegalArgumentException(
                            ERR_UNRECOGNIZED_JSON_VALUE.get(value.getClass().getName()));
                }
            }
        };
@@ -208,6 +213,26 @@
        return s != null ? s.toLowerCase(Locale.ENGLISH) : null;
    }
    static NotSupportedException newNotSupportedException(final LocalizableMessage message) {
        return new NotSupportedException(message.toString());
    }
    static JsonValueException newJsonValueException(final JsonValue value, final LocalizableMessage message) {
        return new JsonValueException(value, message.toString());
    }
    static LocalizedIllegalArgumentException newLocalizedIllegalArgumentException(final LocalizableMessage message) {
        return new LocalizedIllegalArgumentException(message);
    }
    static BadRequestException newBadRequestException(final LocalizableMessage message) {
        return newBadRequestException(message, null);
    }
    static BadRequestException newBadRequestException(final LocalizableMessage message, final Throwable cause) {
        return new BadRequestException(message.toString(), cause);
    }
    private static <T> List<T> asList(final Collection<T> c) {
        if (c instanceof List) {
            return (List<T>) c;
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java
@@ -15,6 +15,7 @@
 */
package org.forgerock.opendj.rest2ldap.authz;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.asConditionalFilter;
import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.newConditionalFilter;
import static org.forgerock.util.promise.Promises.newResultPromise;
@@ -266,8 +267,9 @@
                try {
                    authz.put(template.getSecurityContextID(), template.formatAsAuthzId(token.asJsonValue()));
                } catch (final IllegalArgumentException e) {
                    return newResultPromise(
                            new Response().setStatus(Status.FORBIDDEN).setCause(e).setEntity("Invalid configuration"));
                    return newResultPromise(new Response().setStatus(Status.FORBIDDEN)
                                                          .setCause(e)
                                                          .setEntity(ERR_AUTHZID_DECODER_RESPONSE.get().toString()));
                }
                final Context securityContext = new SecurityContext(context, token.getToken(), authz);
                return next.handle(securityContext, request);
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
@@ -15,6 +15,8 @@
 */
package org.forgerock.opendj.rest2ldap.authz;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.rest2ldap.authz.Utils.newIllegalArgumentException;
import static org.forgerock.util.Utils.joinAsString;
import java.util.ArrayList;
@@ -87,8 +89,8 @@
                    return type;
                }
            }
            throw new IllegalArgumentException("Invalid authorization ID template: '" + template + "'. Templates must "
                       + "start with one of the following elements: " + joinAsString(",", getSupportedStartKeys()));
            throw newIllegalArgumentException(
                    ERR_CONFIG_INVALID_AUTHZID_TEMPLATE.get(template, joinAsString(",", getSupportedStartKeys())));
        }
        private static List<String> getSupportedStartKeys() {
@@ -162,16 +164,12 @@
            final String key = keys.get(i);
            final JsonValue value = principals.get(new JsonPointer(key));
            if (value == null) {
                throw new IllegalArgumentException(String.format(
                        "The request could not be authorized because the required "
                                + "security principal '%s' could not be determined", key));
                throw newIllegalArgumentException(ERR_AUTHZID_DECODER_PRINCIPAL_CANNOT_BE_DETERMINED.get(key));
            }
            final Object object = value.getObject();
            if (!isJSONPrimitive(object)) {
                throw new IllegalArgumentException(String.format(
                        "The request could not be authorized because the required "
                                + "security principal '%s' had an invalid data type", key));
                throw newIllegalArgumentException(ERR_AUTHZID_DECODER_PRINCIPAL_INVALID_DATA_TYPE.get(key));
            }
            values[i] = String.valueOf(object);
        }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CtsAccessTokenResolver.java
@@ -15,8 +15,10 @@
 */
package org.forgerock.opendj.rest2ldap.authz;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.ldap.requests.Requests.newSingleEntrySearchRequest;
import static org.forgerock.opendj.rest2ldap.authz.Utils.close;
import static org.forgerock.opendj.rest2ldap.authz.Utils.newAccessTokenException;
import static org.forgerock.util.Reject.checkNotNull;
import java.io.IOException;
@@ -78,8 +80,7 @@
                    final String tokenName = getRequiredFirstValue(accessToken.get("tokenName"));
                    if (!tokenName.equals("access_token")) {
                        throw new AccessTokenException(
                                "The token '" + token + "' must be an access token, but it is a \"" + tokenName + "\"");
                        throw newAccessTokenException(ERR_OAUTH2_CTS_INVALID_TOKEN_TYPE.get(token, tokenName));
                    }
                    return new AccessTokenInfo(accessToken, token,
@@ -89,14 +90,12 @@
            }, new Function<LdapException, AccessTokenInfo, AccessTokenException>() {
                @Override
                public AccessTokenInfo apply(final LdapException e) throws AccessTokenException {
                    throw new AccessTokenException("Unable to find the token '" + token + "' in the CTS because: "
                            + e.getMessage(), e);
                    throw newAccessTokenException(ERR_OAUTH2_CTS_TOKEN_NOT_FOUND.get(token, e.getMessage()), e);
                }
            }).thenCatchRuntimeException(new Function<RuntimeException, AccessTokenInfo, AccessTokenException>() {
                @Override
                public AccessTokenInfo apply(final RuntimeException e) throws AccessTokenException {
                    throw new AccessTokenException("Unable to resolve access token '" + token
                            + "' due to the following reason: " + e.getMessage(), e);
                    throw newAccessTokenException(ERR_OAUTH2_CTS_TOKEN_RESOLUTION.get(token, e.getMessage()), e);
                }
            }).thenFinally(close(connectionHolder));
    }
@@ -109,7 +108,7 @@
        try {
            return new JsonValue(Json.readJson(accessTokenJson));
        } catch (final IOException e) {
            throw new AccessTokenException("Json of token '" + token + "' is malformed");
            throw newAccessTokenException(ERR_OAUTH2_CTS_INVALID_JSON_TOKEN.get(token));
        }
    }
}
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/FileAccessTokenResolver.java
@@ -15,6 +15,8 @@
 */
package org.forgerock.opendj.rest2ldap.authz;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.rest2ldap.authz.Utils.newAccessTokenException;
import static org.forgerock.util.Reject.checkNotNull;
import static org.forgerock.util.promise.Promises.newExceptionPromise;
import static org.forgerock.util.promise.Promises.newResultPromise;
@@ -48,7 +50,7 @@
        try (final InputStream stream = new FileInputStream(new File(folderPath, token))) {
            accessToken = new JsonValue(Json.readJsonLenient(stream));
        } catch (final IOException e) {
            return newExceptionPromise(new AccessTokenException("Unable to find token file '" + token + "'", e));
            return newExceptionPromise(newAccessTokenException(ERR_OAUTH2_FILE_NO_TOKEN.get(token) , e));
        }
        try {
@@ -58,7 +60,7 @@
            return newResultPromise(result);
        } catch (final JsonValueException e) {
            return newExceptionPromise(
                    new AccessTokenException("Malformed token file '" + token + "': '" + e.getMessage() + "'.", e));
                    newAccessTokenException(ERR_OAUTH2_FILE_INVALID_JSON_TOKEN.get(token, e.getMessage()), e));
        }
    }
}
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Rfc7662AccessTokenResolver.java
@@ -15,7 +15,9 @@
 */
package org.forgerock.opendj.rest2ldap.authz;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.forgerock.opendj.rest2ldap.authz.Utils.newAccessTokenException;
import static org.forgerock.util.Reject.checkNotNull;
import java.io.IOException;
@@ -99,19 +101,18 @@
            public AccessTokenInfo apply(final Response response) throws AccessTokenException {
                final Status status = response.getStatus();
                if (!Status.OK.equals(status)) {
                    throw new AccessTokenException(
                            "Authorization server returned an error: " + status, response.getCause());
                    throw newAccessTokenException(
                            ERR_OAUTH2_RFC7662_RETURNED_ERROR.get(status), response.getCause());
                }
                try (final Entity entity = response.getEntity()) {
                    final JsonValue jsonResponse = asJson(entity);
                    if (!jsonResponse.get(RFC_7662_RESPONSE_ACTIVE_FIELD).defaultTo(Boolean.FALSE).asBoolean()) {
                        throw new AccessTokenException(
                                "Access token returned by authorization server is not currently active");
                        throw newAccessTokenException(ERR_OAUTH2_RFC7662_TOKEN_NOT_ACTIVE.get());
                    }
                    return buildAccessTokenFromJson(jsonResponse, tokenSent);
                } catch (final JsonValueException e) {
                    throw new AccessTokenException("Invalid or malformed access token: " + e.getMessage(), e);
                    throw newAccessTokenException(ERR_OAUTH2_RFC7662_INVALID_JSON_TOKEN.get(e.getMessage()), e);
                }
            }
        };
@@ -129,7 +130,7 @@
            return new JsonValue(entity.getJson());
        } catch (final IOException e) {
            // Do not use Entity.toString(), we probably don't want to fully output the content here
            throw new AccessTokenException("Cannot read response content as JSON", e);
            throw newAccessTokenException(ERR_OAUTH2_RFC7662_CANNOT_READ_RESPONSE.get(), e);
        }
    }
}
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java
@@ -21,8 +21,10 @@
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.authz.modules.oauth2.AccessTokenException;
import org.forgerock.http.protocol.Response;
import org.forgerock.http.protocol.Status;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
@@ -32,6 +34,18 @@
    private Utils() { }
    static IllegalArgumentException newIllegalArgumentException(final LocalizableMessage message) {
        return new IllegalArgumentException(message.toString());
    }
    static AccessTokenException newAccessTokenException(final LocalizableMessage message) {
        return newAccessTokenException(message, null);
    }
    static AccessTokenException newAccessTokenException(final LocalizableMessage message, final Exception cause) {
        return new AccessTokenException(message.toString(), cause);
    }
    static Runnable close(final AtomicReference<? extends Closeable> holder) {
        return new Runnable() {
            @Override
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties
New file
@@ -0,0 +1,110 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
# Configuration errors
ERR_FAIL_PARSE_CONFIGURATION_1=Unable to start Rest2Ldap Http Application due to the configuration error: '%s'
ERR_CONFIG_OAUTH2_UNSUPPORTED_ACCESS_TOKEN_RESOLVER_2= '%s'is not a supported access token resolver. Must be one of '%s'
ERR_CONIFG_OAUTH2_INVALID_INTROSPECT_URL_3=The token introspection endpoint '%s' is not a valid URL: '%s'
ERR_CONIFG_OAUTH2_CACHE_ZERO_DURATION_4=The cache expiration duration cannot be zero
ERR_CONIFG_OAUTH2_CACHE_UNLIMITED_DURATION_5=The cache expiration duration cannot be unlimited
ERR_CONFIG_OAUTH2_CACHE_INVALID_DURATION_6=Malformed duration value '%s' for cache expiration. \
 The duration syntax supports all human readable notations from day ('days'', 'day'', 'd'') to nanosecond \
 ('nanoseconds', 'nanosecond', 'nanosec', 'nanos', 'nano', 'ns')
ERR_CONFIG_UNSUPPORTED_BIND_STRATEGY_7=Unsupported bind strategy '%s'. Must be one of '%s'
ERR_CONFIG_INVALID_AUTHZID_TEMPLATE_8=Invalid authorization ID template: '%s'.\
 Templates must start with one of the following elements: '%s'
ERR_CONFIG_INVALID_AUTHENTICATION_9=Only simple authentication is supported
ERR_CONFIG_INVALID_SECONDARY_LDAP_SERVER_10=secondaryLDAPServers configuration must contain a json array
ERR_CONFIG_SERVER_CIRCULAR_DEPENDENCIES_11=The LDAP server configuration '%s' could not be parsed because of \
 potential circular inheritance dependencies
ERR_CONFIG_NAMING_STRATEGY_DN_AND_ID_NOT_DIFFERENT_12=Naming strategy DN and ID must be different
ERR_CONFIG_NO_MAPPINGS_PROVIDED_13=No mappings provided in configuration
ERR_CONFIG_NO_MAPPING_IN_CONFIGURATION_14=Unknown mapping. The configuration mapping must be one of '%s'
ERR_CONFIG_UNKNOWN_WRITABILITY_15=Unknown writability '%s'. Must be one of %s
ERR_CONFIG_UNKNOWN_NAMING_CONFIGURATION_16=Unknown naming strategy '%s'. Must be one of %s
# Runtime errors
ERR_AUTHZID_DECODER_PRINCIPAL_CANNOT_BE_DETERMINED_17=The request could not be authorized because the required \
 security principal '%s' could not be determined
ERR_AUTHZID_DECODER_PRINCIPAL_INVALID_DATA_TYPE_18=The request could not be authorized because the required \
 security principal '%s' had an invalid data type
ERR_AUTHZID_DECODER_RESPONSE_19=Invalid configuration
ERR_RESOLVING_AUTHZID_TEMPLATE_20=Unable to resolve oauthzid template placeholders for access token '%s': '%s'
# Runtime errors > OAuth2 > CTS Resolver
ERR_OAUTH2_CTS_INVALID_TOKEN_TYPE_21=The token '%s' must be an access token, but it is a '%s'
ERR_OAUTH2_CTS_TOKEN_NOT_FOUND_22=Unable to find the token '%s' in the CTS because: '%s'
ERR_OAUTH2_CTS_TOKEN_RESOLUTION_23=Unable to resolve access token '%s' due to the following reason: '%s'
ERR_OAUTH2_CTS_INVALID_JSON_TOKEN_24=Json of token '%s' is malformed
# Runtime errors > OAuth2 > File Resolver
ERR_OAUTH2_FILE_NO_TOKEN_25=Unable to find token file '%s'
ERR_OAUTH2_FILE_INVALID_JSON_TOKEN_26=Malformed token file '%s'. Details: '%s'
# Runtime errors > OAuth2 > RFC-7662 Resolver
ERR_OAUTH2_RFC7662_RETURNED_ERROR_27=Authorization server returned an error: '%s'
ERR_OAUTH2_RFC7662_TOKEN_NOT_ACTIVE_28=Access token returned by authorization server is not currently active
ERR_OAUTH2_RFC7662_INVALID_JSON_TOKEN_29=Invalid or malformed access token: '%s'
ERR_OAUTH2_RFC7662_CANNOT_READ_RESPONSE_30=Cannot read response content as JSON
# Runtime rest to ldap processing
ERR_CREATION_READ_ONLY_FIELD_31=The create request cannot be processed because it attempts to create the \
 read-only field '%s'
ERR_REMOVE_REQUIRED_FIELD_32=The %s request cannot be processed because it attempts to remove the required field '%s'
ERR_MODIFY_READ_ONLY_FIELD_33=The %s request cannot be processed because it attempts to modify the read-only field '%s'
ERR_PATCH_READ_ONLY_FIELD_34=The patch request cannot be processed because it attempts to patch the read-only field '%s'
ERR_ARRAY_FOR_SINGLE_VALUED_FIELD_35=The request cannot be processed because an array of values was \
 provided for the single valued field '%s'
ERR_NO_ARRAY_FOR_MULTI_VALUED_FIELD_36=The request cannot be processed because an array of values was \
 not provided for the multi-valued field '%s'
ERR_PATCH_APPEND_IN_SINGLE_VALUED_FIELD_37=The patch request cannot be processed because it attempts to append a \
 value to the single valued field '%s'
ERR_PATCH_INDEXED_APPEND_TO_MULTI_VALUED_FIELD_38=The patch request cannot be processed because it attempts to perform \
 an indexed append of an array of values to the multi-valued field '%s'
ERR_PATCH_INDEXED_OPERATION_39=The patch request cannot be processed because it included an indexed patch \
 operation '%s' which is not supported by this resource provider
ERR_UNRECOGNIZED_FIELD_40=The request cannot be processed because it included an unrecognized field '%s'
ERR_FIELD_WRONG_TYPE_41=The request cannot be processed because it included the field '%s' whose value \
 is the wrong type: an object is expected
ERR_PATCH_ADD_NO_VALUE_FOR_FIELD_42=The patch request cannot be processed because it included an add patch operation \
 but no value(s) for field '%s'
ERR_PATCH_UNSUPPORTED_OPERATION_43=The patch request cannot be processed because it included an unsupported \
 type of patch operation '%s'
ERR_MVCC_NOT_SUPPORTED_44=Multi-version concurrency control is not supported by this resource
ERR_MVCC_NO_VERSION_INFORMATION_45=The resource could not be accessed because it did not contain any version \
 information, when the version '%s' was expected
ERR_MVCC_VERSIONS_MISMATCH_46=The resource could not be accessed because the expected version '%s' does \
 not match the current version '%s'
ERR_REFERENCE_FIELD_NO_PRIMARY_KEY_47=The request cannot be processed because the reference field '%s' \
 contains a value which does not contain a primary key
ERR_REFERENCE_FIELD_MULTIPLE_PRIMARY_KEYS_48=The request cannot be processed because the reference field \
 '%s' contains a value which contains multiple primary keys
ERR_REFERENCE_FIELD_DOES_NOT_EXIST_49=The request cannot be processed because the resource '%s' referenced \
 in field '%s' does not exist
ERR_REFERENCE_FIELD_AMBIGUOUS_50=The request cannot be processed because the resource '%s' referenced in \
 field '%s' is ambiguous
ERR_ILLEGAL_FILTER_ASSERTION_VALUE_51=The request cannot be processed because it contained an illegal \
 filter assertion value '%s' for field '%s'
ERR_ENCODING_VALUES_FOR_FIELD_52=The request cannot be processed because an error occurred while encoding \
 the values for the field '%s': '%s'
ERR_UNRECOGNIZED_JSON_VALUE_53=Unrecognized type of JSON value: '%s'
ERR_CLIENT_PROVIDER_RESOURCE_ID_MISSING_54=Resources cannot be created without a client provided resource ID
ERR_NOT_YET_IMPLEMENTED_55=Not yet implemented
ERR_ACTION_NOT_SUPPORTED_56=The action '%s' is not supported
ERR_PASSWORD_MODIFY_SECURE_CONNECTION_57=Password modify requires a secure connection
ERR_PASSWORD_MODIFY_USER_AUTHENTICATED_58=Password modify requires user to be authenticated
ERR_DECODING_CONTROL_59=Unable to decode ldap control: '%s'
ERR_ERROR_RESPONSE_60=An error occurred while processing the request '%s': '%s' (details: '%s')
ERR_RUNTIME_EXCEPTION_61=A runtime exception occurred wile processing the request '%s': '%s'
ERR_PASSWORD_MODIFY_REQUEST_IS_INVALID_62=The password modify request has been rejected because it is invalid. \
 A password modify request may contain two string valued fields 'oldPassword' and 'newPassword'
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_ca_ES.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_de.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_es.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_fr.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_ja.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_ko.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_pl.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_zh_CN.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap_zh_TW.properties
New file
@@ -0,0 +1,15 @@
#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions Copyright [year] [name of copyright owner]".
#
# Copyright 2016 ForgeRock AS.
#
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CtsAccessTokenResolverTestCase.java
@@ -122,7 +122,7 @@
    }
    @Test(expectedExceptions = ExecutionException.class, expectedExceptionsMessageRegExp =
                  ".*The token 'test-token' must be an access token, but it is a \"refresh_token\"")
                  ".*The token 'test-token' must be an access token, but it is a 'refresh_token'")
    public void testInvalidTokenType() throws Exception {
        final SearchResultEntry entry = mock(SearchResultEntry.class);
        final Attribute attribute = mock(Attribute.class);