/**
* $Revision: 1722 $
* $Date: 2005-07-28 15:19:16 -0700 (Thu, 28 Jul 2005) $
*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.plugin.rest;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.security.Security;
import javax.ws.rs.core.Response;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.http.HttpBindManager;
import org.jivesoftware.openfire.plugin.rest.entity.SystemProperties;
import org.jivesoftware.openfire.plugin.rest.entity.SystemProperty;
import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType;
import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException;
import org.jivesoftware.openfire.plugin.ofmeet.jetty.OfMeetLoginService;
import org.jivesoftware.openfire.plugin.ofmeet.sasl.OfMeetSaslProvider;
import org.jivesoftware.openfire.plugin.ofmeet.sasl.OfMeetSaslServer;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.plugin.rest.service.JerseyWrapper;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.util.security.*;
import org.eclipse.jetty.security.*;
import org.eclipse.jetty.security.authentication.*;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
/**
* The Class ChatApiPlugin.
*/
public class ChatApiPlugin implements Plugin, PropertyEventListener {
/** The Constant INSTANCE. */
public static ChatApiPlugin INSTANCE;
private static final String CUSTOM_AUTH_FILTER_PROPERTY_NAME = "chatapi.restapi.customAuthFilter";
/** The secret. */
private String secret;
/** The allowed i ps. */
private Collection<String> allowedIPs;
/** The enabled. */
private boolean enabled;
/** The http auth. */
private String httpAuth;
/** The custom authentication filter */
private String customAuthFilterClassName;
/**
* Gets the single instance of ChatApiPlugin.
*
* @return single instance of ChatApiPlugin
*/
public static ChatApiPlugin getInstance() {
return INSTANCE;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager, java.io.File)
*/
public void initializePlugin(PluginManager manager, File pluginDirectory) {
INSTANCE = this;
secret = JiveGlobals.getProperty("chatapi.restapi.secret", "");
// If no secret key has been assigned, assign a random one.
if ("".equals(secret)) {
secret = StringUtils.randomString(16);
setSecret(secret);
}
// See if Custom authentication filter has been defined
customAuthFilterClassName = JiveGlobals.getProperty("chatapi.restapi.customAuthFilter", "");
// See if the service is enabled or not.
enabled = JiveGlobals.getBooleanProperty("chatapi.restapi.enabled", true);
// See if the HTTP Basic Auth is enabled or not.
httpAuth = JiveGlobals.getProperty("chatapi.restapi.httpAuth", "basic");
// Get the list of IP addresses that can use this service. An empty list
// means that this filter is disabled.
allowedIPs = StringUtils.stringToCollection(JiveGlobals.getProperty("chatapi.restapi.allowedIPs", ""));
// Listen to system property events
PropertyEventDispatcher.addListener(this);
// start REST service on http-bind port
ContextHandlerCollection contexts = HttpBindManager.getInstance().getContexts();
ServletContextHandler context = new ServletContextHandler(contexts, "/rest", ServletContextHandler.SESSIONS);
context.setClassLoader(this.getClass().getClassLoader());
ServletHolder restHolder = new ServletHolder(new JerseyWrapper());
context.addServlet(restHolder, "/api/*");
ServletHolder sseHolder = new ServletHolder(new RestEventSourceServlet());
sseHolder.setAsyncSupported(true);
context.addServlet(sseHolder, "/sse");
// Ensure the JSP engine is initialized correctly (in order to be
// able to cope with Tomcat/Jasper precompiled JSPs).
final List<ContainerInitializer> initializers = new ArrayList<>();
initializers.add(new ContainerInitializer(new JettyJasperInitializer(), null));
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
context.setSecurityHandler(basicAuth("ofmeet"));
WebAppContext context2 = new WebAppContext(contexts, pluginDirectory.getPath(), "/apps");
context2.setClassLoader(this.getClass().getClassLoader());
// Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs).
final List<ContainerInitializer> initializers2 = new ArrayList<>();
initializers2.add(new ContainerInitializer(new JettyJasperInitializer(), null));
context2.setAttribute("org.eclipse.jetty.containerInitializers", initializers2);
context2.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
context2.setWelcomeFiles(new String[]{"index.jsp"});
context2.setSecurityHandler(basicAuth("ofmeet"));
Security.addProvider( new OfMeetSaslProvider() );
SASLAuthentication.addSupportedMechanism( OfMeetSaslServer.MECHANISM_NAME );
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.container.Plugin#destroyPlugin()
*/
public void destroyPlugin()
{
// Stop listening to system property events
PropertyEventDispatcher.removeListener(this);
SASLAuthentication.removeSupportedMechanism( OfMeetSaslServer.MECHANISM_NAME );
Security.removeProvider( OfMeetSaslProvider.NAME );
}
/**
* Gets the system properties.
*
* @return the system properties
*/
public SystemProperties getSystemProperties() {
SystemProperties systemProperties = new SystemProperties();
List<SystemProperty> propertiesList = new ArrayList<SystemProperty>();
for(String propertyKey : JiveGlobals.getPropertyNames()) {
String propertyValue = JiveGlobals.getProperty(propertyKey);
propertiesList.add(new SystemProperty(propertyKey, propertyValue));
}
systemProperties.setProperties(propertiesList);
return systemProperties;
}
/**
* Gets the system property.
*
* @param propertyKey the property key
* @return the system property
* @throws ServiceException the service exception
*/
public SystemProperty getSystemProperty(String propertyKey) throws ServiceException {
String propertyValue = JiveGlobals.getProperty(propertyKey);
if(propertyValue != null) {
return new SystemProperty(propertyKey, propertyValue);
} else {
throw new ServiceException("Could not find property", propertyKey, ExceptionType.PROPERTY_NOT_FOUND,
Response.Status.NOT_FOUND);
}
}
/**
* Creates the system property.
*
* @param systemProperty the system property
*/
public void createSystemProperty(SystemProperty systemProperty) {
JiveGlobals.setProperty(systemProperty.getKey(), systemProperty.getValue());
}
/**
* Delete system property.
*
* @param propertyKey the property key
* @throws ServiceException the service exception
*/
public void deleteSystemProperty(String propertyKey) throws ServiceException {
if(JiveGlobals.getProperty(propertyKey) != null) {
JiveGlobals.deleteProperty(propertyKey);
} else {
throw new ServiceException("Could not find property", propertyKey, ExceptionType.PROPERTY_NOT_FOUND,
Response.Status.NOT_FOUND);
}
}
/**
* Update system property.
*
* @param propertyKey the property key
* @param systemProperty the system property
* @throws ServiceException the service exception
*/
public void updateSystemProperty(String propertyKey, SystemProperty systemProperty) throws ServiceException {
if(JiveGlobals.getProperty(propertyKey) != null) {
if(systemProperty.getKey().equals(propertyKey)) {
JiveGlobals.setProperty(propertyKey, systemProperty.getValue());
} else {
throw new ServiceException("Path property name and entity property name doesn't match", propertyKey, ExceptionType.ILLEGAL_ARGUMENT_EXCEPTION,
Response.Status.BAD_REQUEST);
}
} else {
throw new ServiceException("Could not find property for update", systemProperty.getKey(), ExceptionType.PROPERTY_NOT_FOUND,
Response.Status.NOT_FOUND);
}
}
/**
* 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.
*
* @return the secret key.
*/
public String getSecret() {
return secret;
}
/**
* Sets the secret key that grants permission to use the userservice.
*
* @param secret
* the secret key.
*/
public void setSecret(String secret) {
JiveGlobals.setProperty("chatapi.restapi.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.
*
* @return the allowed i ps
*/
public Collection<String> getAllowedIPs() {
allowedIPs = StringUtils.stringToCollection(JiveGlobals.getProperty("chatapi.restapi.allowedIPs", ""));
return allowedIPs;
}
/**
* Sets the allowed i ps.
*
* @param allowedIPs the new allowed i ps
*/
public void setAllowedIPs(Collection<String> allowedIPs) {
JiveGlobals.setProperty("chatapi.restapi.allowedIPs", StringUtils.collectionToString(allowedIPs));
this.allowedIPs = allowedIPs;
}
/**
* Returns true if the user service is enabled. If not enabled, it will not
* accept requests to create new accounts.
*
* @return true if the user service is enabled.
*/
public boolean isEnabled() {
enabled = JiveGlobals.getBooleanProperty("chatapi.restapi.enabled", true);
return enabled;
}
/**
* Enables or disables the user service. If not enabled, it will not accept
* requests to create new accounts.
*
* @param enabled
* true if the user service should be enabled.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
JiveGlobals.setProperty("chatapi.restapi.enabled", enabled ? "true" : "false");
}
/**
* Gets the http authentication mechanism.
*
* @return the http authentication mechanism
*/
public String getHttpAuth() {
httpAuth = JiveGlobals.getProperty("chatapi.restapi.httpAuth", "basic");
return httpAuth;
}
/**
* Sets the http auth.
*
* @param httpAuth the new http auth
*/
public void setHttpAuth(String httpAuth) {
this.httpAuth = httpAuth;
JiveGlobals.setProperty("chatapi.restapi.httpAuth", httpAuth);
}
/* (non-Javadoc)
* @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map)
*/
public void propertySet(String property, Map params) {
if (property.equals("chatapi.restapi.secret")) {
this.secret = (String) params.get("value");
} else if (property.equals("chatapi.restapi.enabled")) {
this.enabled = Boolean.parseBoolean((String) params.get("value"));
} else if (property.equals("chatapi.restapi.allowedIPs")) {
this.allowedIPs = StringUtils.stringToCollection((String) params.get("value"));
} else if (property.equals("chatapi.restapi.httpAuth")) {
this.httpAuth = (String) params.get("value");
} else if(property.equals(CUSTOM_AUTH_FILTER_PROPERTY_NAME)) {
this.customAuthFilterClassName = (String) params.get("value");
}
}
/* (non-Javadoc)
* @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, java.util.Map)
*/
public void propertyDeleted(String property, Map params) {
if (property.equals("chatapi.restapi.secret")) {
this.secret = "";
} else if (property.equals("chatapi.restapi.enabled")) {
this.enabled = false;
} else if (property.equals("chatapi.restapi.allowedIPs")) {
this.allowedIPs = Collections.emptyList();
} else if (property.equals("chatapi.restapi.httpAuth")) {
this.httpAuth = "basic";
} else if(property.equals(CUSTOM_AUTH_FILTER_PROPERTY_NAME)) {
this.customAuthFilterClassName = null;
}
}
/* (non-Javadoc)
* @see org.jivesoftware.util.PropertyEventListener#xmlPropertySet(java.lang.String, java.util.Map)
*/
public void xmlPropertySet(String property, Map params) {
// Do nothing
}
/* (non-Javadoc)
* @see org.jivesoftware.util.PropertyEventListener#xmlPropertyDeleted(java.lang.String, java.util.Map)
*/
public void xmlPropertyDeleted(String property, Map params) {
// Do nothing
}
private static final SecurityHandler basicAuth(String realm) {
final OfMeetLoginService loginService = new OfMeetLoginService();
loginService.setName(realm);
final Constraint constraint = new Constraint();
constraint.setName( Constraint.__BASIC_AUTH );
constraint.setRoles( new String[] { "ofmeet" } );
constraint.setAuthenticate( true );
final ConstraintMapping constraintMapping = new ConstraintMapping();
constraintMapping.setConstraint( constraint );
constraintMapping.setPathSpec( "/*" );
final ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setAuthenticator( new BasicAuthenticator() );
securityHandler.setRealmName( realm );
securityHandler.addConstraintMapping( constraintMapping );
securityHandler.setLoginService( loginService );
return securityHandler;
}
}