Repository JAX-RS Service
Repository JAX-RS Service
The Hippo Repository provides a RepositoryJaxrsService which supports dynamic adding and removing JAX-RS REST Application endpoints.
These Application endpoints can be accessed through a corresponding RepositoryJaxrsServlet which by default is configured in the Hippo CMS application web.xml under /ws/<endpoint address>. The Hippo Replication and Synchronization Enterprise addons are two examples of Hippo Repository modules which dynamically register REST endpoints through the RepositoryJaxrsService. By default REST endpoints registered at the RepositoryJaxrsService are secured with basic authentication via the Hippo Repository, i.e. require a valid Repository (CMS) username and password. In addition authorization restrictions can be configured for each REST endpoint.
Configuration
The hippo-repository-jaxrs dependency is required to enable and use the RepositoryJaxrsService, and by default already is included in the Hippo CMS web application via the hippo-repository-dependencies pom dependency:
<dependency> <groupId>org.onehippo.cms7</groupId> <artifactId>hippo-repository-jaxrs</artifactId> </dependency>
The RepositoryJaxrsServlet also by default already is configured for the CMS application web.xml as follows:
<servlet> <servlet-name>RepositoryJaxrsServlet</servlet-name> <servlet-class>org.onehippo.repository.jaxrs.RepositoryJaxrsServlet</servlet-class> <load-on-startup>6</load-on-startup> </servlet> ... <servlet-mapping> <servlet-name>RepositoryJaxrsServlet</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping>
How to define and register a Repository REST endpoint
The hippo-repository-jaxrs jar dependency provides a Java RepositoryJaxrsEndpoint builder class which is used to define and register a JAX-RS Application instance (or just one or more JAX-RS Resources) as endpoint with the RepositoryJaxrsService at a specific (endpoint) address. The typical use-case for a Repository REST endpoint is a Repository managed daemon module providing a REST API to interact with and manage it. A trivial "Hello World" example of such a daemon module would be:
package com.example.hello; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import org.onehippo.repository.jaxrs.RepositoryJaxrsEndpoint; import org.onehippo.repository.jaxrs.RepositoryJaxrsService; import org.onehippo.repository.modules.DaemonModule; public class HelloModule implements DaemonModule { @Override public void initialize(final Session session) throws RepositoryException { RepositoryJaxrsService.addEndpoint( new RepositoryJaxrsEndpoint("/hello").singleton(new HelloResource())); } @Override public void shutdown() { RepositoryJaxrsService.removeEndpoint("/hello"); } public static class HelloResource { @Path("/") @GET public String sayHello(@QueryParam("name") String name) { return "Hello " + name +"!"; } } }
When this example daemon module is deployed with the CMS it will automatically be started by the Repository and then itself dynamically register its JAX-RS HelloResource endpoint at address: /hello. This endpoint then can be accessed through for example http://localhost:8080/cms/ws/hello?name=world which then will return (after authentication with a valid CMS username and password): Hello world!
Building a RepositoryJaxrsEndpoint
A RepositoryJaxrsEndpoint builder instance is created with the root address for the target endpoint, like for example "/hello". Note that the RepositoryJaxrsService will fail to add an endpoint on an address already in use, so make sure to choose and claim a unique and dedicated address for your endpoint. A endpoint address must start with a "/", which will be ensured by the builder itself by automatically prefixing a "/" if needed. Such a builder instance then can further be configured with either a JAX-RS Application instance like:
RepositoryJaxrsEndpoint endpoint = new RepositoryJaxrsEndpoint("/").app(myJaxrsApplication);
or with separate, multiple if desired, JAX-RS resource, provider or feature root classes and/or singletons (corresponding with those provided by an Application instance), like:
RepositoryJaxrsEndpoint endpoint = new RepositoryJaxrsEndpoint("/") .rootClass(MyRootClass.class) .rootClass(OtherRootClass.class) .singleton(mySingletonResource) .singleton(otherSingletonResource);
Building a Apache CXF specific CXFRepositoryJaxrsEndpoint
The RepositoryJaxrsService uses Apache CXF as JAX-RS engine, and Apache CXF supports additional extensions through so called CXF Interceptors. With a CXFRepositoryJaxrsEndpoint extending the RepositoryJaxrsEndpoint, such CXF specific Interceptors can be configured as well, like:
RepositoryJaxrsEndpoint endpoint = new CXFRepositoryJaxrsEndpoint("/") .inInterceptor(myInInterceptor) .outInterceptor(myOutInterceptor) .singleton(mySingletonResource);
Customizing or overriding the REST endpoint authentication
By default the RepositoryJaxrsService will configure every REST endpoint to be basic authenticated against the Hippo Repository, using the provided username and password to (only) login to the repository. The authentication (and authorization, see further below) handling is configurable and overridable, per REST endpoint when using a CXFRepositoryJaxrsEndpoint builder. The authentication, and optional authorization, is handled by a custom CXF JAXRSInvoker providing pre/post processing of a request invocation. The default authentication is provided by the AuthenticatingRepositoryJaxrsInvoker, which enforces a repository login before proceding with the request handling. The CXFRepositoryJaxrsEndpoint builder allows configuring a custom JAXRSInvoker, thereby providing complete freedom for custom request pre/post processing, including a custom authentication/authorization implementation:
RepositoryJaxrsEndpoint endpoint = new CXFRepositoryJaxrsEndpoint("/") .invoker(new AuthenticatingRepositoryJaxrsInvoker()) // the default .singleton(mySingletonResource);
Note that configuring a null invoker actually will result in the default invoker being set, it will not disable the authentication. If that is needed, you should configure the default (CXF) JaxrsInvoker instead:
RepositoryJaxrsEndpoint endpoint = new CXFRepositoryJaxrsEndpoint("/") .invoker(new org.apache.cxf.jaxrs.JAXRSInvoker()) // default CXF JARSInvoker .singleton(mySingletonResource);
Configuring REST endpoint authorization
Besides enforcing authentication a REST endpoint can also be configured to require additional, repository based, authorization. The RepositoryJaxrsEndpoint builder provides a fluent authorized(String authorizationNodePath, String authorizationPermission) method through which a specific Hippo Repository permission can be specified to be checked against a specific node path, for the authenticated user. The RepositoryJaxrsService.HIPPO_REST_PERMISSION is a convenient and predefined (and bootstrapped) permission for this purpose:
public static final String HIPPO_REST_PERMISSION = "hippo:rest";
When configuring REST endpoint(s) for a Repository daemon module, you can for example use the daemon module path for authorization, like:
package org.example.hello; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import org.onehippo.repository.jaxrs.RepositoryJaxrsEndpoint; import org.onehippo.repository.jaxrs.RepositoryJaxrsService; import org.onehippo.repository.modules.AbstractReconfigurableDaemonModule; import static org.onehippo.repository.jaxrs.RepositoryJaxrsService.HIPPO_REST_PERMISSION; public class HelloModule extends AbstractReconfigurableDaemonModule { private String modulePath; @Override protected void doConfigure(final Node moduleConfig) throws RepositoryException { modulePath = moduleConfig.getParent().getPath(); } @Override public void initialize(final Session session) throws RepositoryException { RepositoryJaxrsService.addEndpoint( new RepositoryJaxrsEndpoint("/hello") .singleton(new HelloResource()) .authorized(modulePath, HIPPO_REST_PERMISSION); } ...
How to setup and configure a security domain using the HIPPO_REST_PERMISSION on the daemon module path or some other path, which grants selected users access to its REST endpoint is described at Repository Authorization and Permissions. The hippo-repository-jaxrs module predefines and bootstraps a restuser role having the HIPPO_REST_PERMISSION, which can be used for setting up such a security domain for authorizing a Repository REST endpoint. The authorization handling is performed by a AuthorizingRepositoryJaxrsInvoker, which extends the default AuthenticatingRepositoryJaxrsInvoker. Note that when overriding the endpoint JAXRSInvoker with a custom invoker, handling authentication and authorization also becomes the responsibility of this custom invoker.
Two notes about the the repository authorization model:
- The repository authorization model is implemented such that checking for a permission on a path that does not exist for any user always succeeds. In other words, it is highly recommended to use a path that is guaranteed to exist for the authorization check. For Repository daemon modules, best practise is to use the modules configuration root for this.
- A security domain can be configured in such a way that users do have the HIPPO_REST_PERMISSION privilege, but do not have read access for a certain path.