Commit 9ee97728 authored by Roman S's avatar Roman S

Merge pull request #363 from totalscoccia/master

RestAPI Plugin - Provide the possibility to use Custom Auth Filter
parents 12f5bd54 ce10f45a
...@@ -77,7 +77,7 @@ public class AuthFilter implements ContainerRequestFilter { ...@@ -77,7 +77,7 @@ public class AuthFilter implements ContainerRequestFilter {
} }
// HTTP Basic Auth or Shared Secret key // HTTP Basic Auth or Shared Secret key
if (plugin.isHttpBasicAuth()) { if ("basic".equals(plugin.getHttpAuth())) {
String[] usernameAndPassword = BasicAuth.decode(auth); String[] usernameAndPassword = BasicAuth.decode(auth);
// If username or password fail // If username or password fail
......
...@@ -39,6 +39,8 @@ import org.jivesoftware.util.PropertyEventDispatcher; ...@@ -39,6 +39,8 @@ import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener; import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.plugin.rest.service.JerseyWrapper;
/** /**
* The Class RESTServicePlugin. * The Class RESTServicePlugin.
*/ */
...@@ -47,6 +49,8 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -47,6 +49,8 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
/** The Constant INSTANCE. */ /** The Constant INSTANCE. */
public static final RESTServicePlugin INSTANCE = new RESTServicePlugin(); public static final RESTServicePlugin INSTANCE = new RESTServicePlugin();
private static final String CUSTOM_AUTH_FILTER_PROPERTY_NAME = "plugin.restapi.customAuthFilter";
/** The secret. */ /** The secret. */
private String secret; private String secret;
...@@ -56,8 +60,12 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -56,8 +60,12 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
/** The enabled. */ /** The enabled. */
private boolean enabled; private boolean enabled;
/** The http basic auth. */ /** The http auth. */
private boolean httpBasicAuth; private String httpAuth;
/** The custom authentication filter */
private String customAuthFilterClassName;
/** /**
* Gets the single instance of RESTServicePlugin. * Gets the single instance of RESTServicePlugin.
...@@ -79,11 +87,14 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -79,11 +87,14 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
setSecret(secret); setSecret(secret);
} }
// See if Custom authentication filter has been defined
customAuthFilterClassName = JiveGlobals.getProperty("plugin.restapi.customAuthFilter", "");
// See if the service is enabled or not. // See if the service is enabled or not.
enabled = JiveGlobals.getBooleanProperty("plugin.restapi.enabled", false); enabled = JiveGlobals.getBooleanProperty("plugin.restapi.enabled", false);
// See if the HTTP Basic Auth is enabled or not. // See if the HTTP Basic Auth is enabled or not.
httpBasicAuth = JiveGlobals.getBooleanProperty("plugin.restapi.httpAuth.enabled", false); httpAuth = JiveGlobals.getProperty("plugin.restapi.httpAuth", "basic");
// Get the list of IP addresses that can use this service. An empty list // Get the list of IP addresses that can use this service. An empty list
// means that this filter is disabled. // means that this filter is disabled.
...@@ -181,6 +192,23 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -181,6 +192,23 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
} }
} }
/**
* Returns the loading status message.
*
* @return the loading status message.
*/
public String getLoadingStatusMessage() {
return JerseyWrapper.getLoadingStatusMessage();
}
/**
* Reloads the Jersey wrapper.
*/
public String loadAuthenticationFilter(String customAuthFilterClassName) {
return JerseyWrapper.tryLoadingAuthenticationFilter(customAuthFilterClassName);
}
/** /**
* Returns the secret key that only valid requests should know. * Returns the secret key that only valid requests should know.
* *
...@@ -201,6 +229,26 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -201,6 +229,26 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
this.secret = secret; this.secret = secret;
} }
/**
* Returns the custom authentication filter class name used in place of the basic ones to grant permission to use the Rest services.
*
* @return custom authentication filter class name .
*/
public String getCustomAuthFilterClassName() {
return customAuthFilterClassName;
}
/**
* Sets the customAuthFIlterClassName used to grant permission to use the Rest services.
*
* @param customAuthFilterClassName
* custom authentication filter class name.
*/
public void setCustomAuthFiIterClassName(String customAuthFilterClassName) {
JiveGlobals.setProperty(CUSTOM_AUTH_FILTER_PROPERTY_NAME, customAuthFilterClassName);
this.customAuthFilterClassName = customAuthFilterClassName;
}
/** /**
* Gets the allowed i ps. * Gets the allowed i ps.
* *
...@@ -243,22 +291,22 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -243,22 +291,22 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
} }
/** /**
* Checks if is http basic auth. * Gets the http authentication mechanism.
* *
* @return true, if is http basic auth * @return the http authentication mechanism
*/ */
public boolean isHttpBasicAuth() { public String getHttpAuth() {
return httpBasicAuth; return httpAuth;
} }
/** /**
* Sets the http basic auth. * Sets the http auth.
* *
* @param httpBasicAuth the new http basic auth * @param httpAuth the new http auth
*/ */
public void setHttpBasicAuth(boolean httpBasicAuth) { public void setHttpAuth(String httpAuth) {
this.httpBasicAuth = httpBasicAuth; this.httpAuth = httpAuth;
JiveGlobals.setProperty("plugin.restapi.httpAuth.enabled", httpBasicAuth ? "true" : "false"); JiveGlobals.setProperty("plugin.restapi.httpAuth", httpAuth);
} }
/* (non-Javadoc) /* (non-Javadoc)
...@@ -271,8 +319,10 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -271,8 +319,10 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
this.enabled = Boolean.parseBoolean((String) params.get("value")); this.enabled = Boolean.parseBoolean((String) params.get("value"));
} else if (property.equals("plugin.restapi.allowedIPs")) { } else if (property.equals("plugin.restapi.allowedIPs")) {
this.allowedIPs = StringUtils.stringToCollection((String) params.get("value")); this.allowedIPs = StringUtils.stringToCollection((String) params.get("value"));
} else if (property.equals("plugin.restapi.httpAuth.enabled")) { } else if (property.equals("plugin.restapi.httpAuth")) {
this.httpBasicAuth = Boolean.parseBoolean((String) params.get("value")); this.httpAuth = (String) params.get("value");
} else if(property.equals(CUSTOM_AUTH_FILTER_PROPERTY_NAME)) {
this.customAuthFilterClassName = (String) params.get("value");
} }
} }
...@@ -286,8 +336,10 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { ...@@ -286,8 +336,10 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener {
this.enabled = false; this.enabled = false;
} else if (property.equals("plugin.restapi.allowedIPs")) { } else if (property.equals("plugin.restapi.allowedIPs")) {
this.allowedIPs = Collections.emptyList(); this.allowedIPs = Collections.emptyList();
} else if (property.equals("plugin.restapi.httpAuth.enabled")) { } else if (property.equals("plugin.restapi.httpAuth")) {
this.httpBasicAuth = false; this.httpAuth = "basic";
} else if(property.equals(CUSTOM_AUTH_FILTER_PROPERTY_NAME)) {
this.customAuthFilterClassName = null;
} }
} }
......
...@@ -5,11 +5,15 @@ import java.util.Map; ...@@ -5,11 +5,15 @@ import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.lang.ClassNotFoundException;
import java.lang.Class;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import org.jivesoftware.admin.AuthCheckFilter; import org.jivesoftware.admin.AuthCheckFilter;
import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper; import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper;
import org.jivesoftware.util.JiveGlobals;
import com.sun.jersey.api.core.PackagesResourceConfig; import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer; import com.sun.jersey.spi.container.servlet.ServletContainer;
...@@ -19,6 +23,12 @@ import com.sun.jersey.spi.container.servlet.ServletContainer; ...@@ -19,6 +23,12 @@ import com.sun.jersey.spi.container.servlet.ServletContainer;
*/ */
public class JerseyWrapper extends ServletContainer { public class JerseyWrapper extends ServletContainer {
/** The Constant CUSTOM_AUTH_PROPERTY_NAME */
private static final String CUSTOM_AUTH_PROPERTY_NAME = "plugin.restapi.customAuthFilter";
/** The Constant REST_AUTH_TYPE */
private static final String REST_AUTH_TYPE = "plugin.restapi.httpAuth";
/** The Constant AUTHFILTER. */ /** The Constant AUTHFILTER. */
private static final String AUTHFILTER = "org.jivesoftware.openfire.plugin.rest.AuthFilter"; private static final String AUTHFILTER = "org.jivesoftware.openfire.plugin.rest.AuthFilter";
...@@ -55,14 +65,16 @@ public class JerseyWrapper extends ServletContainer { ...@@ -55,14 +65,16 @@ public class JerseyWrapper extends ServletContainer {
/** The Constant JERSEY_LOGGER. */ /** The Constant JERSEY_LOGGER. */
private final static Logger JERSEY_LOGGER = Logger.getLogger("com.sun.jersey"); private final static Logger JERSEY_LOGGER = Logger.getLogger("com.sun.jersey");
private static String loadingStatusMessage = null;
static { static {
JERSEY_LOGGER.setLevel(Level.SEVERE); JERSEY_LOGGER.setLevel(Level.SEVERE);
config = new HashMap<String, Object>(); config = new HashMap<String, Object>();
config.put(RESOURCE_CONFIG_CLASS_KEY, RESOURCE_CONFIG_CLASS); config.put(RESOURCE_CONFIG_CLASS_KEY, RESOURCE_CONFIG_CLASS);
prc = new PackagesResourceConfig(SCAN_PACKAGE_DEFAULT); prc = new PackagesResourceConfig(SCAN_PACKAGE_DEFAULT);
prc.setPropertiesAndFeatures(config); prc.setPropertiesAndFeatures(config);
prc.getProperties().put(CONTAINER_REQUEST_FILTERS, AUTHFILTER);
prc.getProperties().put(CONTAINER_RESPONSE_FILTERS, CORSFILTER); prc.getProperties().put(CONTAINER_RESPONSE_FILTERS, CORSFILTER);
loadAuthenticationFilter();
prc.getClasses().add(RestAPIService.class); prc.getClasses().add(RestAPIService.class);
...@@ -87,6 +99,43 @@ public class JerseyWrapper extends ServletContainer { ...@@ -87,6 +99,43 @@ public class JerseyWrapper extends ServletContainer {
prc.getClasses().add(RESTExceptionMapper.class); prc.getClasses().add(RESTExceptionMapper.class);
} }
public static String tryLoadingAuthenticationFilter(String customAuthFilterClassName) {
try {
if(customAuthFilterClassName != null) {
Class.forName(customAuthFilterClassName, false, JerseyWrapper.class.getClassLoader());
loadingStatusMessage = null;
}
} catch (ClassNotFoundException e) {
loadingStatusMessage = "No custom auth filter found for restAPI plugin with name " + customAuthFilterClassName;
}
if(customAuthFilterClassName == null || customAuthFilterClassName.isEmpty())
loadingStatusMessage = "Classname field can't be empty!";
return loadingStatusMessage;
}
public static String loadAuthenticationFilter() {
// Check if custom AuthFilter is available
String customAuthFilterClassName = JiveGlobals.getProperty(CUSTOM_AUTH_PROPERTY_NAME);
String restAuthType = JiveGlobals.getProperty(REST_AUTH_TYPE);
String pickedAuthFilter = AUTHFILTER;
try {
if(customAuthFilterClassName != null && "custom".equals(restAuthType)) {
Class.forName(customAuthFilterClassName, false, JerseyWrapper.class.getClassLoader());
pickedAuthFilter = customAuthFilterClassName;
loadingStatusMessage = null;
}
} catch (ClassNotFoundException e) {
loadingStatusMessage = "No custom auth filter found for restAPI plugin! " + customAuthFilterClassName + " " + restAuthType;
}
prc.getProperties().put(CONTAINER_REQUEST_FILTERS, pickedAuthFilter);
return loadingStatusMessage;
}
/** /**
* Instantiates a new jersey wrapper. * Instantiates a new jersey wrapper.
*/ */
...@@ -101,6 +150,7 @@ public class JerseyWrapper extends ServletContainer { ...@@ -101,6 +150,7 @@ public class JerseyWrapper extends ServletContainer {
*/ */
@Override @Override
public void init(ServletConfig servletConfig) throws ServletException { public void init(ServletConfig servletConfig) throws ServletException {
loadAuthenticationFilter();
super.init(servletConfig); super.init(servletConfig);
// Exclude this servlet from requering the user to login // Exclude this servlet from requering the user to login
AuthCheckFilter.addExclude(SERVLET_URL); AuthCheckFilter.addExclude(SERVLET_URL);
...@@ -117,4 +167,14 @@ public class JerseyWrapper extends ServletContainer { ...@@ -117,4 +167,14 @@ public class JerseyWrapper extends ServletContainer {
// Release the excluded URL // Release the excluded URL
AuthCheckFilter.removeExclude(SERVLET_URL); AuthCheckFilter.removeExclude(SERVLET_URL);
} }
/*
* Returns the loading status message.
*
* @return the loading status message.
*/
public static String getLoadingStatusMessage() {
return loadingStatusMessage;
}
} }
<%@ page <%@ page
import="java.util.*, import="java.util.*,
org.jivesoftware.openfire.XMPPServer, org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.util.*,org.jivesoftware.openfire.plugin.rest.RESTServicePlugin" org.jivesoftware.util.*,org.jivesoftware.openfire.plugin.rest.RESTServicePlugin,
org.jivesoftware.openfire.container.Plugin,
org.jivesoftware.openfire.container.PluginManager"
errorPage="error.jsp"%> errorPage="error.jsp"%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%> <%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
...@@ -20,8 +22,13 @@ ...@@ -20,8 +22,13 @@
boolean success = request.getParameter("success") != null; boolean success = request.getParameter("success") != null;
String secret = ParamUtils.getParameter(request, "secret"); String secret = ParamUtils.getParameter(request, "secret");
boolean enabled = ParamUtils.getBooleanParameter(request, "enabled"); boolean enabled = ParamUtils.getBooleanParameter(request, "enabled");
boolean httpBasicAuth = ParamUtils.getBooleanParameter(request, "authtype"); String httpAuth = ParamUtils.getParameter(request, "authtype");
String allowedIPs = ParamUtils.getParameter(request, "allowedIPs"); String allowedIPs = ParamUtils.getParameter(request, "allowedIPs");
String customAuthFilterClassName = ParamUtils.getParameter(request, "customAuthFilterClassName");
String loadingStatus = null;
final PluginManager pluginManager = admin.getXMPPServer().getPluginManager();
RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager() RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager()
.getPlugin("restapi"); .getPlugin("restapi");
...@@ -29,11 +36,31 @@ ...@@ -29,11 +36,31 @@
// Handle a save // Handle a save
Map errors = new HashMap(); Map errors = new HashMap();
if (save) { if (save) {
if("custom".equals(httpAuth)) {
loadingStatus = plugin.loadAuthenticationFilter(customAuthFilterClassName);
}
if (loadingStatus != null) {
errors.put("loadingStatus", loadingStatus);
}
if (errors.size() == 0) { if (errors.size() == 0) {
boolean is2Reload = "custom".equals(httpAuth) || "custom".equals(plugin.getHttpAuth());
plugin.setEnabled(enabled); plugin.setEnabled(enabled);
plugin.setSecret(secret); plugin.setSecret(secret);
plugin.setHttpBasicAuth(httpBasicAuth); plugin.setHttpAuth(httpAuth);
plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs));
plugin.setCustomAuthFiIterClassName(customAuthFilterClassName);
if(is2Reload) {
String pluginName = pluginManager.getName(plugin);
String pluginDir = pluginManager.getPluginDirectory(plugin).getName();
pluginManager.unloadPlugin(pluginDir);
// Log the event
admin.logEvent("reloaded plugin "+ pluginName, null);
response.sendRedirect("/plugin-admin.jsp?reloadsuccess=true");
}
response.sendRedirect("rest-api.jsp?success=true"); response.sendRedirect("rest-api.jsp?success=true");
return; return;
} }
...@@ -41,8 +68,9 @@ ...@@ -41,8 +68,9 @@
secret = plugin.getSecret(); secret = plugin.getSecret();
enabled = plugin.isEnabled(); enabled = plugin.isEnabled();
httpBasicAuth = plugin.isHttpBasicAuth(); httpAuth = plugin.getHttpAuth();
allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs());
customAuthFilterClassName = plugin.getCustomAuthFilterClassName();
%> %>
<html> <html>
...@@ -76,6 +104,24 @@ ...@@ -76,6 +104,24 @@
} }
%> %>
<%
if (errors.get("loadingStatus") != null) {
%>
<div class="jive-error">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/error-16x16.gif"
width="16" height="16" border="0"></td>
<td class="jive-icon-label"><%= loadingStatus %></td>
</tr>
</tbody>
</table>
</div>
<br>
<%
}
%>
<form action="rest-api.jsp?save" method="post"> <form action="rest-api.jsp?save" method="post">
<fieldset> <fieldset>
...@@ -101,13 +147,13 @@ ...@@ -101,13 +147,13 @@
<br> <br>
<br> <br>
<input type="radio" name="authtype" value="true" <input type="radio" name="authtype" value="basic"
id="http_basic_auth" <%=((httpBasicAuth) ? "checked" : "")%>> id="http_basic_auth" <%=("basic".equals(httpAuth) ? "checked" : "")%>>
<label for="http_basic_auth">HTTP basic auth - REST API <label for="http_basic_auth">HTTP basic auth - REST API
authentication with Openfire admin account.</label> authentication with Openfire admin account.</label>
<br> <br>
<input type="radio" name="authtype" value="false" <input type="radio" name="authtype" value="secret"
id="secretKeyAuth" <%=((!httpBasicAuth) ? "checked" : "")%>> id="secretKeyAuth" <%=("secret".equals(httpAuth) ? "checked" : "")%>>
<label for="secretKeyAuth">Secret key auth - REST API <label for="secretKeyAuth">Secret key auth - REST API
authentication over specified secret key.</label> authentication over specified secret key.</label>
<br> <br>
...@@ -116,6 +162,17 @@ ...@@ -116,6 +162,17 @@
<input type="text" name="secret" value="<%=secret%>" <input type="text" name="secret" value="<%=secret%>"
id="text_secret"> id="text_secret">
<br> <br>
<input type="radio" name="authtype" value="custom"
id="customFilterAuth" <%=("custom".equals(httpAuth) ? "checked" : "")%>>
<label for="secretKeyAuth">Custom authentication filter classname - REST API
authentication delegates to a custom filter implemented in some other plugin.
</label>
<div style="margin-left: 20px; margin-top: 5px;"><strong>Note: changing back and forth from custom authentication filter forces the REST API plugin reloading</strong></div>
<label style="padding-left: 25px" for="text_secret">Filter
classname:</label>
<input type="text" name="customAuthFilterClassName" value="<%= customAuthFilterClassName %>"
id="custom_auth_filter_class_name" style="width:70%;padding:4px;">
<br>
<br> <br>
<label for="allowedIPs">Allowed IP Addresses:</label> <label for="allowedIPs">Allowed IP Addresses:</label>
......
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