Commit abc44238 authored by Dave Cridland's avatar Dave Cridland

Merge pull request #120 from Redor/openfire

REST API plugin version 0.1.0
parents b943b620 81cf5c87
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>REST API Plugin Changelog</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
padding-left : 1em;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
</style>
</head>
<body>
<h1>
REST API Plugin Changelog
</h1>
<p><b>0.1.0</b> -- November 14th, 2014</p>
<ul>
<li>Initial release of REST API Plugin with possibility to manage system properties.</li>
</ul>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.jivesoftware.openfire.plugin.rest.RESTServicePlugin</class>
<name>REST API</name>
<description>Allows administration over a RESTful API.</description>
<author>Roman Soldatow</author>
<version>0.1.0</version>
<date>11/14/2014</date>
<minServerVersion>3.9.0</minServerVersion>
<adminconsole>
<tab id="tab-server">
<sidebar id="sidebar-server-settings">
<item id="rest-api" name="REST API" url="rest-api.jsp"
description="Click to manage the service that allows to configure the Openfire over a RESTFul API" />
</sidebar>
</tab>
</adminconsole>
</plugin>
This source diff could not be displayed because it is too large. You can view the blob instead.
package org.jivesoftware.openfire.plugin.rest;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response.Status;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.admin.AdminManager;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.auth.ConnectionException;
import org.jivesoftware.openfire.auth.InternalUnauthenticatedException;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
/**
* The Class AuthFilter.
*/
public class AuthFilter implements ContainerRequestFilter {
/** The log. */
private static Logger LOG = LoggerFactory.getLogger(AuthFilter.class);
/** The http request. */
@Context
private HttpServletRequest httpRequest;
/** The plugin. */
private RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager()
.getPlugin("restapi");
/*
* (non-Javadoc)
*
* @see
* com.sun.jersey.spi.container.ContainerRequestFilter#filter(com.sun.jersey
* .spi.container.ContainerRequest)
*/
@Override
public ContainerRequest filter(ContainerRequest containerRequest) throws WebApplicationException {
if (!plugin.isEnabled()) {
throw new WebApplicationException(Status.FORBIDDEN);
}
if (!plugin.getAllowedIPs().isEmpty()) {
// Get client's IP address
String ipAddress = httpRequest.getHeader("x-forwarded-for");
if (ipAddress == null) {
ipAddress = httpRequest.getHeader("X_FORWARDED_FOR");
if (ipAddress == null) {
ipAddress = httpRequest.getHeader("X-Forward-For");
if (ipAddress == null) {
ipAddress = httpRequest.getRemoteAddr();
}
}
}
if (!plugin.getAllowedIPs().contains(ipAddress)) {
LOG.warn("REST API rejected service to IP address: " + ipAddress);
throw new WebApplicationException(Status.UNAUTHORIZED);
}
}
// Get the authentification passed in HTTP headers parameters
String auth = containerRequest.getHeaderValue("authorization");
if (auth == null) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
// HTTP Basic Auth or Shared Secret key
if (plugin.isHttpBasicAuth()) {
String[] usernameAndPassword = BasicAuth.decode(auth);
// If username or password fail
if (usernameAndPassword == null || usernameAndPassword.length != 2) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
boolean userAdmin = AdminManager.getInstance().isUserAdmin(usernameAndPassword[0], true);
if (!userAdmin) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
try {
AuthFactory.authenticate(usernameAndPassword[0], usernameAndPassword[1]);
} catch (UnauthorizedException e) {
LOG.warn("Wrong HTTP Basic Auth authorization", e);
throw new WebApplicationException(Status.UNAUTHORIZED);
} catch (ConnectionException e) {
throw new WebApplicationException(Status.UNAUTHORIZED);
} catch (InternalUnauthenticatedException e) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
} else {
if (!auth.equals(plugin.getSecret())) {
LOG.warn("Wrong secret key authorization. Provided key: " + auth);
throw new WebApplicationException(Status.UNAUTHORIZED);
}
}
return containerRequest;
}
}
package org.jivesoftware.openfire.plugin.rest;
import javax.xml.bind.DatatypeConverter;
public class BasicAuth {
/**
* Decode the basic auth and convert it to array login/password
*
* @param auth
* The string encoded authentification
* @return The login (case 0), the password (case 1)
*/
public static String[] decode(String auth) {
// Replacing "Basic THE_BASE_64" to "THE_BASE_64" directly
auth = auth.replaceFirst("[B|b]asic ", "");
// Decode the Base64 into byte[]
byte[] decodedBytes = DatatypeConverter.parseBase64Binary(auth);
// If the decode fails in any case
if (decodedBytes == null || decodedBytes.length == 0) {
return null;
}
// Now we can convert the byte[] into a splitted array :
// - the first one is login,
// - the second one password
return new String(decodedBytes).split(":", 2);
}
}
package org.jivesoftware.openfire.plugin.rest;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import org.jivesoftware.admin.AuthCheckFilter;
import org.jivesoftware.openfire.plugin.rest.service.RestAPIService;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;
/**
* The Class JerseyWrapper.
*/
public class JerseyWrapper extends ServletContainer {
/** The Constant AUTHFILTER. */
private static final String AUTHFILTER = "org.jivesoftware.openfire.plugin.rest.AuthFilter";
/** The Constant CONTAINER_REQUEST_FILTERS. */
private static final String CONTAINER_REQUEST_FILTERS = "com.sun.jersey.spi.container.ContainerRequestFilters";
/** The Constant RESOURCE_CONFIG_CLASS_KEY. */
private static final String RESOURCE_CONFIG_CLASS_KEY = "com.sun.jersey.config.property.resourceConfigClass";
/** The Constant RESOURCE_CONFIG_CLASS. */
private static final String RESOURCE_CONFIG_CLASS = "com.sun.jersey.api.core.PackagesResourceConfig";
/** The Constant SCAN_PACKAGE_DEFAULT. */
private static final String SCAN_PACKAGE_DEFAULT = JerseyWrapper.class.getPackage().getName();
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The Constant SERVLET_URL. */
private static final String SERVLET_URL = "restapi/*";
/** The config. */
private static Map<String, Object> config;
/** The prc. */
private static PackagesResourceConfig prc;
static {
config = new HashMap<String, Object>();
config.put(RESOURCE_CONFIG_CLASS_KEY, RESOURCE_CONFIG_CLASS);
prc = new PackagesResourceConfig(SCAN_PACKAGE_DEFAULT);
prc.setPropertiesAndFeatures(config);
prc.getProperties().put(CONTAINER_REQUEST_FILTERS, AUTHFILTER);
prc.getClasses().add(RestAPIService.class);
prc.getClasses().add(RESTExceptionMapper.class);
}
/**
* Instantiates a new jersey wrapper.
*/
public JerseyWrapper() {
super(prc);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
// Exclude this servlet from requering the user to login
AuthCheckFilter.addExclude(SERVLET_URL);
}
/*
* (non-Javadoc)
*
* @see com.sun.jersey.spi.container.servlet.ServletContainer#destroy()
*/
@Override
public void destroy() {
super.destroy();
// Release the excluded URL
AuthCheckFilter.removeExclude(SERVLET_URL);
}
}
package org.jivesoftware.openfire.plugin.rest;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.jivesoftware.openfire.plugin.rest.exceptions.ErrorResponse;
import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class RESTExceptionMapper.
*/
@Provider
public class RESTExceptionMapper implements ExceptionMapper<ServiceException> {
/** The log. */
private static Logger LOG = LoggerFactory.getLogger(RESTExceptionMapper.class);
/**
* Instantiates a new REST exception mapper.
*/
public RESTExceptionMapper() {
}
/*
* (non-Javadoc)
*
* @see javax.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable)
*/
public Response toResponse(ServiceException exception) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setRessource(exception.getRessource());
errorResponse.setMessage(exception.getMessage());
errorResponse.setException(exception.getException());
LOG.error(
exception.getException() + ": " + exception.getMessage() + " with ressource "
+ exception.getRessource(), exception.getException());
return Response.status(exception.getStatus()).entity(errorResponse).type(MediaType.APPLICATION_XML).build();
}
}
package org.jivesoftware.openfire.plugin.rest.entity;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* The Class SystemProperties.
*/
@XmlRootElement(name = "properties")
public class SystemProperties {
/** The properties. */
List<SystemProperty> properties;
/**
* Instantiates a new system properties.
*/
public SystemProperties() {
}
/**
* Gets the properties.
*
* @return the properties
*/
@XmlElement(name = "property")
public List<SystemProperty> getProperties() {
return properties;
}
/**
* Sets the properties.
*
* @param properties the new properties
*/
public void setProperties(List<SystemProperty> properties) {
this.properties = properties;
}
}
package org.jivesoftware.openfire.plugin.rest.entity;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
/**
* The Class SystemProperty.
*/
@XmlRootElement(name = "property")
public class SystemProperty {
/** The key. */
private String key;
/** The value. */
private String value;
/**
* Instantiates a new system property.
*/
public SystemProperty() {
}
/**
* Instantiates a new system property.
*
* @param key the key
* @param value the value
*/
public SystemProperty(String key, String value) {
this.key = key;
this.value = value;
}
/**
* Gets the key.
*
* @return the key
*/
@XmlAttribute
public String getKey() {
return key;
}
/**
* Sets the key.
*
* @param key
* the new key
*/
public void setKey(String key) {
this.key = key;
}
/**
* Gets the value.
*
* @return the value
*/
@XmlAttribute
public String getValue() {
return value;
}
/**
* Sets the value.
*
* @param value
* the new value
*/
public void setValue(String value) {
this.value = value;
}
}
package org.jivesoftware.openfire.plugin.rest.exceptions;
import javax.xml.bind.annotation.XmlRootElement;
/**
* The Class ErrorResponse.
*/
@XmlRootElement(name = "error")
public class ErrorResponse {
/** The ressource. */
private String ressource;
/** The message. */
private String message;
/** The exception. */
private String exception;
/** The exception stack. */
private String exceptionStack;
/**
* Gets the ressource.
*
* @return the ressource
*/
public String getRessource() {
return ressource;
}
/**
* Sets the ressource.
*
* @param ressource
* the new ressource
*/
public void setRessource(String ressource) {
this.ressource = ressource;
}
/**
* Gets the message.
*
* @return the message
*/
public String getMessage() {
return message;
}
/**
* Sets the message.
*
* @param message
* the new message
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Gets the exception.
*
* @return the exception
*/
public String getException() {
return exception;
}
/**
* Sets the exception.
*
* @param exception
* the new exception
*/
public void setException(String exception) {
this.exception = exception;
}
/**
* Gets the exception stack.
*
* @return the exception stack
*/
public String getExceptionStack() {
return exceptionStack;
}
/**
* Sets the exception stack.
*
* @param exceptionStack
* the new exception stack
*/
public void setExceptionStack(String exceptionStack) {
this.exceptionStack = exceptionStack;
}
}
\ No newline at end of file
package org.jivesoftware.openfire.plugin.rest.exceptions;
/**
* The Class ExceptionType.
*/
public final class ExceptionType {
/** The Constant PROPERTY_NOT_FOUND. */
public static final String PROPERTY_NOT_FOUND = "PropertyNotFoundException";
/** The Constant ILLEGAL_ARGUMENT_EXCEPTION. */
public static final String ILLEGAL_ARGUMENT_EXCEPTION = "IllegalArgumentException";
/**
* Instantiates a new exception type.
*/
private ExceptionType() {
}
}
package org.jivesoftware.openfire.plugin.rest.exceptions;
import javax.ws.rs.core.Response.Status;
/**
* The Class ServiceException.
*/
public class ServiceException extends Exception {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 4351720088030656859L;
/** The ressource. */
private String ressource;
/** The exception. */
private String exception;
/** The status. */
private Status status;
/**
* Instantiates a new service exception.
*
* @param msg the msg
* @param ressource the ressource
* @param exception the exception
* @param status the status
*/
public ServiceException(String msg, String ressource, String exception, Status status) {
super(msg);
this.ressource = ressource;
this.exception = exception;
this.status = status;
}
/**
* Instantiates a new service exception.
*
* @param msg the msg
* @param ressource the ressource
* @param exception the exception
* @param status the status
* @param cause the cause
*/
public ServiceException(String msg, String ressource, String exception, Status status, Throwable cause) {
super(msg, cause);
this.ressource = ressource;
this.exception = exception;
this.status = status;
}
/**
* Gets the ressource.
*
* @return the ressource
*/
public String getRessource() {
return ressource;
}
/**
* Sets the ressource.
*
* @param ressource
* the new ressource
*/
public void setRessource(String ressource) {
this.ressource = ressource;
}
/**
* Gets the exception.
*
* @return the exception
*/
public String getException() {
return exception;
}
/**
* Sets the exception.
*
* @param exception
* the new exception
*/
public void setException(String exception) {
this.exception = exception;
}
/**
* Gets the status.
*
* @return the status
*/
public Status getStatus() {
return status;
}
/**
* Sets the status.
*
* @param status
* the new status
*/
public void setStatus(Status status) {
this.status = status;
}
}
\ No newline at end of file
package org.jivesoftware.openfire.plugin.rest.service;
import javax.annotation.PostConstruct;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jivesoftware.openfire.plugin.rest.RESTServicePlugin;
import org.jivesoftware.openfire.plugin.rest.entity.SystemProperties;
import org.jivesoftware.openfire.plugin.rest.entity.SystemProperty;
import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException;
@Path("restapi/system/properties")
public class RestAPIService {
private RESTServicePlugin plugin;
@PostConstruct
public void init() {
plugin = RESTServicePlugin.getInstance();
}
@GET
@Produces(MediaType.APPLICATION_XML)
public SystemProperties getSystemProperties() {
return plugin.getSystemProperties();
}
@GET
@Path("/{propertyKey}")
@Produces(MediaType.APPLICATION_XML)
public SystemProperty getSystemProperty(@PathParam("propertyKey") String propertyKey) throws ServiceException {
return plugin.getSystemProperty(propertyKey);
}
@POST
public Response createSystemProperty(SystemProperty systemProperty) throws ServiceException {
plugin.createSystemProperty(systemProperty);
return Response.status(Response.Status.CREATED).build();
}
@PUT
@Path("/{propertyKey}")
public Response updateUser(@PathParam("propertyKey") String propertyKey, SystemProperty systemProperty) throws ServiceException {
plugin.updateSystemProperty(propertyKey, systemProperty);
return Response.status(Response.Status.OK).build();
}
@DELETE
@Path("/{propertyKey}")
public Response deleteUser(@PathParam("propertyKey") String propertyKey) throws ServiceException {
plugin.deleteSystemProperty(propertyKey);
return Response.status(Response.Status.OK).build();
}
}
<?xml version='1.0' encoding='ISO-8859-1'?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<servlet-name>JerseyWrapper</servlet-name>
<servlet-class>org.jivesoftware.openfire.plugin.rest.JerseyWrapper</servlet-class>
</servlet>
<!-- Servlet mappings -->
<servlet-mapping>
<servlet-name>JerseyWrapper</servlet-name>
<url-pattern>/system/*</url-pattern>
</servlet-mapping>
</web-app>
<%@ page import="java.util.*,
org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.util.*,org.jivesoftware.openfire.plugin.rest.RESTServicePlugin"
errorPage="error.jsp"
%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<%-- Define Administration Bean --%>
<jsp:useBean id="admin" class="org.jivesoftware.util.WebManager" />
<c:set var="admin" value="${admin.manager}" />
<%
admin.init(request, response, session, application, out );
%>
<%
// Get parameters
boolean save = request.getParameter("save") != null;
boolean success = request.getParameter("success") != null;
String secret = ParamUtils.getParameter(request, "secret");
boolean enabled = ParamUtils.getBooleanParameter(request, "enabled");
boolean httpBasicAuth = ParamUtils.getBooleanParameter(request, "authtype");
String allowedIPs = ParamUtils.getParameter(request, "allowedIPs");
RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager().getPlugin("restapi");
// Handle a save
Map errors = new HashMap();
if (save) {
if (errors.size() == 0) {
plugin.setEnabled(enabled);
plugin.setSecret(secret);
plugin.setHttpBasicAuth(httpBasicAuth);
plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs));
response.sendRedirect("rest-api.jsp?success=true");
return;
}
}
secret = plugin.getSecret();
enabled = plugin.isEnabled();
httpBasicAuth = plugin.isHttpBasicAuth();
allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs());
%>
<html>
<head>
<title>REST API Properties</title>
<meta name="pageID" content="rest-api"/>
</head>
<body>
<p>
Use the form below to enable or disable the REST API and configure the authentication.
</p>
<% if (success) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0"></td>
<td class="jive-icon-label">
REST API properties edited successfully.
</td></tr>
</tbody>
</table>
</div><br>
<% } %>
<form action="rest-api.jsp?save" method="post">
<fieldset>
<legend>REST API</legend>
<div>
<p>
The addition, deletion and editing of Openfire system properties is not normally available outside of the admin console.
This service lets those administration tasks be performed HTTP requests to provide
simple integration with other applications.</p>
<p>The REST API can be secured with a shared secret key defined below or a with HTTP basic authentication.
Moreover, for extra security you can specify the list of IP addresses that are allowed to
use this service. An empty list means that the service can be accessed from any
location. Addresses are delimited by commas.
</p>
<ul>
<input type="radio" name="enabled" value="true" id="rb01"
<%= ((enabled) ? "checked" : "") %>>
<label for="rb01"><b>Enabled</b> - REST API requests will be processed.</label>
<br>
<input type="radio" name="enabled" value="false" id="rb02"
<%= ((!enabled) ? "checked" : "") %>>
<label for="rb02"><b>Disabled</b> - REST API requests will be ignored.</label>
<br><br>
<input type="radio" name="authtype" value="true" id="http_basic_auth" <%= ((httpBasicAuth) ? "checked" : "") %>>
<label for="http_basic_auth">HTTP basic auth - REST API authentication with Openfire admin account.</label>
<br>
<input type="radio" name="authtype" value="false" id="secretKeyAuth" <%= ((!httpBasicAuth) ? "checked" : "") %>>
<label for="secretKeyAuth">Secret key auth - REST API authentication over specified secret key.</label>
<br>
<label style="padding-left: 25px" for="text_secret">Secret key:</label>
<input type="text" name="secret" value="<%= secret %>" id="text_secret">
<br><br>
<label for="text_secret">Allowed IP Addresses:</label>
<textarea name="allowedIPs" cols="40" rows="3" wrap="virtual"><%= ((allowedIPs != null) ? allowedIPs : "") %></textarea>
</ul>
</div>
</fieldset>
<br><br>
<input type="submit" value="Save Settings">
</form>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment