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

Matthew Swift
28.19.2016 d28b40a9868880bfeb999250eb7ca6fc731e6a87
OPENDJ-3186 Improve API version support in admin and rest2ldap endpoints

* return a warning header if the Accept-API-Version header is not
present in the request
* add default route to admin and rest2ldap endpoints which is triggered
when the requested API version is not supported. The default route
displays a helpful error message which lists the supported versions
* set admin resource API version to 1.0.
10 files modified
134 ■■■■ changed files
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java 39 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java 7 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java 5 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java 24 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java 10 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java 10 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties 2 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java 29 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java 6 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/messages/org/opends/messages/config.properties 2 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
@@ -25,6 +25,7 @@
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
@@ -34,51 +35,63 @@
/**
 * An abstract base class from which request handlers may be easily implemented. The default implementation of each
 * method is to return the {@link ResourceException} passed in during construction.
 * method is to invoke the {@link #handleRequest(Context, Request)} method.
 */
abstract class AbstractRequestHandler implements RequestHandler {
    private final ResourceException defaultErrorResponse;
    AbstractRequestHandler(final ResourceException defaultErrorResponse) {
        this.defaultErrorResponse = defaultErrorResponse;
public abstract class AbstractRequestHandler implements RequestHandler {
    /** Creates a new {@code AbstractRequestHandler}. */
    protected AbstractRequestHandler() {
        // Nothing to do.
    }
    @Override
    public Promise<ActionResponse, ResourceException> handleAction(final Context context, final ActionRequest request) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    @Override
    public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
                                                                     final CreateRequest request) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    @Override
    public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
                                                                     final DeleteRequest request) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    @Override
    public Promise<ResourceResponse, ResourceException> handlePatch(final Context context, final PatchRequest request) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    @Override
    public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request,
                                                                 final QueryResourceHandler handler) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    @Override
    public Promise<ResourceResponse, ResourceException> handleRead(final Context context, final ReadRequest request) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    @Override
    public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
                                                                     final UpdateRequest request) {
        return defaultErrorResponse.asPromise();
        return handleRequest(context, request);
    }
    /**
     * Implement this method in order to provide a default behavior when processing requests.
     *
     * @param <V>
     *         The type of response.
     * @param context
     *         The request context.
     * @param request
     *         The request.
     * @return A {@code Promise} containing the result of the operation.
     */
    protected abstract <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request);
}
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java
@@ -23,6 +23,7 @@
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
@@ -36,7 +37,6 @@
    private final RequestHandler delegate;
    ReadOnlyRequestHandler(final RequestHandler delegate) {
        super(new BadRequestException(ERR_READ_ONLY_ENDPOINT.get().toString()));
        this.delegate = delegate;
    }
@@ -51,4 +51,9 @@
            final Context context, final ReadRequest request) {
        return delegate.handleRead(context, request);
    }
    @Override
    protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
        return new BadRequestException(ERR_READ_ONLY_ENDPOINT.get().toString()).asPromise();
    }
}
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
@@ -18,6 +18,8 @@
import static org.forgerock.http.handler.HttpClientHandler.OPTION_KEY_MANAGERS;
import static org.forgerock.http.handler.HttpClientHandler.OPTION_TRUST_MANAGERS;
import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionContextFilter;
import static org.forgerock.json.JsonValueFunctions.duration;
import static org.forgerock.json.JsonValueFunctions.enumConstant;
import static org.forgerock.json.JsonValueFunctions.setOf;
@@ -201,7 +203,8 @@
            final Filter authorizationFilter = buildAuthorizationFilter(config.get("authorization").required());
            return Handlers.chainOf(newHttpHandler(configureRest2Ldap(configDirectory)),
                                    new ErrorLoggerFilter(),
                                    authorizationFilter);
                                    authorizationFilter,
                                    resourceApiVersionContextFilter(newResourceApiVersionBehaviourManager()));
        } catch (final Exception e) {
            final LocalizableMessage errorMsg = ERR_FAIL_PARSE_CONFIGURATION.get(e.getLocalizedMessage());
            logger.error(errorMsg, e);
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
@@ -40,6 +40,7 @@
import static org.forgerock.opendj.rest2ldap.Rest2Ldap.*;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException;
import static org.forgerock.util.Utils.joinAsString;
import static org.forgerock.util.time.Duration.duration;
import java.io.BufferedReader;
@@ -64,14 +65,19 @@
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.Router;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.services.context.Context;
import org.forgerock.util.Options;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.time.Duration;
/** Provides core factory methods and builders for constructing Rest2Ldap endpoints from JSON configuration. */
@@ -209,7 +215,6 @@
     */
    public static Router configureEndpoint(final File endpointDirectory, final Options options) throws IOException {
        final Router versionRouter = new Router();
        final File[] endpointVersions = endpointDirectory.listFiles(new FileFilter() {
            @Override
            public boolean accept(final File pathname) {
@@ -221,6 +226,8 @@
            throw new LocalizedIllegalArgumentException(ERR_INVALID_ENDPOINT_DIRECTORY.get(endpointDirectory));
        }
        final List<String> supportedVersions = new ArrayList<>();
        boolean hasWildCardVersion = false;
        for (final File endpointVersion : endpointVersions) {
            final JsonValue mappingConfig = readJson(endpointVersion);
            final String version = mappingConfig.get("version").defaultTo("*").asString();
@@ -234,13 +241,24 @@
            if (version.equals("*")) {
                versionRouter.setDefaultRoute(handler);
                hasWildCardVersion = true;
            } else {
                versionRouter.addRoute(version(version), handler);
                supportedVersions.add(version);
            }
            logger.debug(INFO_REST2LDAP_CREATING_ENDPOINT.get(endpointDirectory.getName(), version));
        }
        if (!hasWildCardVersion) {
            versionRouter.setDefaultRoute(new AbstractRequestHandler() {
                @Override
                protected <V> Promise<V, ResourceException> handleRequest(Context context, Request request) {
                    final String message = ERR_BAD_API_RESOURCE_VERSION.get(request.getResourceVersion(),
                                                                            joinAsString(", ", supportedVersions))
                                                                       .toString();
                    return new BadRequestException(message).asPromise();
                }
            });
        }
        return versionRouter;
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
@@ -41,6 +41,7 @@
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
@@ -356,10 +357,6 @@
     * URL template /collection/{id} then this handler processes requests against /collection.
     */
    private final class CollectionHandler extends AbstractRequestHandler {
        private CollectionHandler() {
            super(new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_COLLECTION.get().toString()));
        }
        @Override
        public Promise<ActionResponse, ResourceException> handleAction(final Context context,
                                                                       final ActionRequest request) {
@@ -377,6 +374,11 @@
                                                                     final QueryResourceHandler handler) {
            return collection(context).query(context, request, handler);
        }
        @Override
        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
            return new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_COLLECTION.get().toString()).asPromise();
        }
    }
    /**
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
@@ -35,6 +35,7 @@
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
@@ -148,10 +149,6 @@
     * both a singleton and also a collection of {child}.
     */
    private final class InstanceHandler extends AbstractRequestHandler {
        private InstanceHandler() {
            super(new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get().toString()));
        }
        @Override
        public Promise<ActionResponse, ResourceException> handleAction(final Context context,
                                                                       final ActionRequest request) {
@@ -200,6 +197,11 @@
            return singleton(context).update(context, null, request);
        }
        @Override
        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
            return new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get().toString()).asPromise();
        }
        private <T> Function<ResourceException, T, ResourceException> convert404To400() {
            return SubResource.convert404To400(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get());
        }
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties
@@ -138,3 +138,5 @@
INFO_REST2LDAP_CREATING_ENDPOINT_81=Rest2Ldap created endpoint '%s' version %s
ERR_PASSWORD_RESET_SECURE_CONNECTION_82=Passwords can only be reset using a secure connection
ERR_PASSWORD_RESET_USER_AUTHENTICATED_83=Passwords can only be reset by authenticated users
ERR_BAD_API_RESOURCE_VERSION_84=The requested resource API version '%s' is unsupported. This endpoint only supports \
  the following resource API version(s): %s
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java
@@ -16,6 +16,10 @@
 */
package org.opends.server.protocols.http.rest2ldap;
import static org.forgerock.http.handler.Handlers.chainOf;
import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionContextFilter;
import static org.forgerock.http.routing.Version.version;
import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax;
@@ -24,6 +28,7 @@
import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY;
import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
import static org.forgerock.util.Options.defaultOptions;
import static org.opends.messages.ConfigMessages.ERR_BAD_ADMIN_API_RESOURCE_VERSION;
import java.util.ArrayList;
import java.util.Collection;
@@ -35,8 +40,13 @@
import org.forgerock.http.HttpApplication;
import org.forgerock.http.HttpApplicationException;
import org.forgerock.http.io.Buffer;
import org.forgerock.http.routing.Version;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.Router;
import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
import org.forgerock.opendj.config.AggregationPropertyDefinition;
import org.forgerock.opendj.config.DefaultBehaviorProvider;
@@ -55,6 +65,7 @@
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Functions;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.rest2ldap.AbstractRequestHandler;
import org.forgerock.opendj.rest2ldap.ReferencePropertyMapper;
import org.forgerock.opendj.rest2ldap.Resource;
import org.forgerock.opendj.rest2ldap.Rest2Ldap;
@@ -64,9 +75,11 @@
import org.forgerock.opendj.server.config.meta.GlobalCfgDefn;
import org.forgerock.opendj.server.config.meta.RootCfgDefn;
import org.forgerock.opendj.server.config.server.AdminEndpointCfg;
import org.forgerock.services.context.Context;
import org.forgerock.util.Factory;
import org.forgerock.util.Function;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
import org.opends.server.api.HttpEndpoint;
import org.opends.server.core.ServerContext;
import org.opends.server.types.InitializationException;
@@ -76,6 +89,7 @@
 */
public final class AdminEndpoint extends HttpEndpoint<AdminEndpointCfg>
{
  private static final Version ADMIN_API_VERSION = version("1.0");
  private static final String TYPE_PROPERTY = "_schema";
  private static final String ADMIN_API = "admin-api";
  private static final String MONITOR = "monitor";
@@ -165,7 +179,20 @@
      final Rest2Ldap rest2Ldap = rest2Ldap(defaultOptions(), resources.values());
      final RequestHandler handler = rest2Ldap.newRequestHandlerFor(ADMIN_API);
      return newHttpHandler(handler);
      final Router versionRouter = new Router();
      versionRouter.addRoute(ADMIN_API_VERSION, handler);
      versionRouter.setDefaultRoute(new AbstractRequestHandler()
      {
        @Override
        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request)
        {
          final String message = ERR_BAD_ADMIN_API_RESOURCE_VERSION.get(request.getResourceVersion(), ADMIN_API_VERSION)
                                                                   .toString();
          return new BadRequestException(message).asPromise();
        }
      });
      return chainOf(newHttpHandler(versionRouter),
                     resourceApiVersionContextFilter(newResourceApiVersionBehaviourManager()));
    }
    private Resource buildResource(final AbstractManagedObjectDefinition<?, ?> mod)
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
@@ -15,6 +15,9 @@
 */
package org.opends.server.protocols.http.rest2ldap;
import static org.forgerock.http.handler.Handlers.chainOf;
import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionContextFilter;
import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
import static org.forgerock.opendj.rest2ldap.Rest2LdapJsonConfigurator.configureEndpoint;
import static org.forgerock.util.Options.defaultOptions;
@@ -76,7 +79,8 @@
      final File endpointConfig = getFileForPath(configuration.getConfigDirectory(), serverContext);
      try
      {
        return newHttpHandler(configureEndpoint(endpointConfig, defaultOptions()));
        return chainOf(newHttpHandler(configureEndpoint(endpointConfig, defaultOptions())),
                       resourceApiVersionContextFilter(newResourceApiVersionBehaviourManager()));
      }
      catch (IOException e)
      {
opendj-server-legacy/src/messages/org/opends/messages/config.properties
@@ -851,3 +851,5 @@
 an invalid URL %s: %s
ERR_CONFIG_OAUTH2_CONFIG_ERROR_756=Unable to configure the authorization mechanism defined \
 in %s: %s
ERR_BAD_ADMIN_API_RESOURCE_VERSION_757=The requested admin API version '%s' is unsupported. This endpoint only \
  supports the following admin API version(s): %s