diff --git a/.gitignore b/.gitignore index 72cf1c33..72810d46 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ broker/runner # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +broker/moquette_store.* diff --git a/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java b/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java index 2079e78d..a41f5123 100644 --- a/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java +++ b/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java @@ -60,6 +60,8 @@ class ConfigurationParser { m_properties.put(PASSWORD_FILE_PROPERTY_NAME, ""); m_properties.put(PERSISTENT_STORE_PROPERTY_NAME, DEFAULT_PERSISTENT_PATH); m_properties.put(ALLOW_ANONYMOUS_PROPERTY_NAME, true); + m_properties.put(AUTHENTICATOR_CLASS_NAME, ""); + m_properties.put(AUTHORIZATOR_CLASS_NAME, ""); } /** diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java index 667c56d6..238bbd4e 100644 --- a/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java @@ -35,14 +35,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.text.ParseException; -import java.util.Collections; -import java.util.List; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.eclipse.moquette.commons.Constants; import static org.eclipse.moquette.commons.Constants.PASSWORD_FILE_PROPERTY_NAME; import static org.eclipse.moquette.commons.Constants.PERSISTENT_STORE_PROPERTY_NAME; @@ -195,36 +196,99 @@ public class SimpleMessaging implements IMessaging, EventHandler { //subscriptions.init(storedSubscriptions); subscriptions.init(m_sessionsStore); - String passwdPath = props.getProperty(PASSWORD_FILE_PROPERTY_NAME, ""); - String configPath = System.getProperty("moquette.path", null); - IAuthenticator authenticator; - if (passwdPath.isEmpty()) { - authenticator = new AcceptAllAuthenticator(); - } else { - authenticator = new FileAuthenticator(configPath, passwdPath); + IAuthenticator authenticator = null; + + String configPath = System.getProperty("moquette.path", null); + String authenticatorClassName = props.getProperty(Constants.AUTHENTICATOR_CLASS_NAME, ""); + + if(!authenticatorClassName.isEmpty()) { + authenticator = (IAuthenticator)loadClass(authenticatorClassName, IAuthenticator.class); + LOG.info("Loaded custom authenticator {}", authenticatorClassName); } - - String aclFilePath = props.getProperty(ACL_FILE_PROPERTY_NAME, ""); - IAuthorizator authorizator; - if (aclFilePath != null && !aclFilePath.isEmpty()) { - authorizator = new DenyAllAuthorizator(); - File aclFile = new File(configPath, aclFilePath); - try { - authorizator = ACLFileParser.parse(aclFile); - } catch (ParseException pex) { - LOG.error(String.format("Format error in parsing acl file %s", aclFile), pex); + + if(authenticator == null) { + String passwdPath = props.getProperty(PASSWORD_FILE_PROPERTY_NAME, ""); + if (passwdPath.isEmpty()) { + authenticator = new AcceptAllAuthenticator(); + } else { + authenticator = new FileAuthenticator(configPath, passwdPath); } - LOG.info("Using acl file defined at path {}", aclFilePath); - } else { - authorizator = new PermitAllAuthorizator(); - LOG.info("Starting without ACL definition"); } + + + IAuthorizator authorizator = null; + String authorizatorClassName = props.getProperty(Constants.AUTHORIZATOR_CLASS_NAME, ""); + if(!authorizatorClassName.isEmpty()) { + authorizator = (IAuthorizator)loadClass(authorizatorClassName, IAuthorizator.class); + LOG.info("Loaded custom authorizator {}", authorizatorClassName); + } + + if(authorizator == null) { + + String aclFilePath = props.getProperty(ACL_FILE_PROPERTY_NAME, ""); + if (aclFilePath != null && !aclFilePath.isEmpty()) { + authorizator = new DenyAllAuthorizator(); + File aclFile = new File(configPath, aclFilePath); + try { + authorizator = ACLFileParser.parse(aclFile); + } catch (ParseException pex) { + LOG.error(String.format("Format error in parsing acl file %s", aclFile), pex); + } + LOG.info("Using acl file defined at path {}", aclFilePath); + } else { + authorizator = new PermitAllAuthorizator(); + LOG.info("Starting without ACL definition"); + } + + } + boolean allowAnonymous = Boolean.parseBoolean(props.getProperty(ALLOW_ANONYMOUS_PROPERTY_NAME, "true")); m_processor.init(subscriptions, m_storageService, m_sessionsStore, authenticator, allowAnonymous, authorizator); } + + private Object loadClass(String className, Class cls) { + + Object instance = null; + + try { + Class clazz = Class.forName(className); + // check if method getInstance exists + Method method = clazz.getMethod("getInstance", new Class[] {}); + try { + instance = method.invoke(null, new Object[] {}); + } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException ex) { + LOG.error(null, ex); + throw new RuntimeException("Cannot call method "+ className +".getInstance", ex); + } + + } + catch (NoSuchMethodException noMethodEx) { + + try { + instance = this.getClass().getClassLoader() + .loadClass(className) + .asSubclass(cls) + .newInstance(); + + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) { + LOG.error(null, ex); + throw new RuntimeException("Cannot load custom authenticator class " + className, ex); + } + + } catch (ClassNotFoundException ex) { + LOG.error(null, ex); + throw new RuntimeException("Class " + className + " not found", ex); + } catch (SecurityException ex) { + LOG.error(null, ex); + throw new RuntimeException("Cannot call method "+ className +".getInstance", ex); + } + + return instance; + } + private void processStop() { LOG.debug("processStop invoked"); m_storageService.close(); diff --git a/broker/src/test/java/org/eclipse/moquette/server/ConfigurationClassLoaderTest.java b/broker/src/test/java/org/eclipse/moquette/server/ConfigurationClassLoaderTest.java new file mode 100644 index 00000000..ef8eb090 --- /dev/null +++ b/broker/src/test/java/org/eclipse/moquette/server/ConfigurationClassLoaderTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2012-2014 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ +package org.eclipse.moquette.server; + +import org.fusesource.mqtt.client.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import static org.eclipse.moquette.commons.Constants.PERSISTENT_STORE_PROPERTY_NAME; +import static org.eclipse.moquette.commons.Constants.AUTHENTICATOR_CLASS_NAME; +import static org.eclipse.moquette.commons.Constants.AUTHORIZATOR_CLASS_NAME; +import org.eclipse.moquette.spi.impl.security.IAuthenticator; +import org.eclipse.moquette.spi.impl.security.IAuthorizator; + +import static org.junit.Assert.*; + +/** + * + * @author luca + */ +public class ConfigurationClassLoaderTest implements IAuthenticator, IAuthorizator { + private static final Logger LOG = LoggerFactory.getLogger(ConfigurationClassLoaderTest.class); + + Server m_server; + MQTT m_mqtt; + BlockingConnection m_subscriber; + BlockingConnection m_publisher; + + protected void startServer(Properties props) throws IOException { + m_server = new Server(); + m_server.startServer(props); + } + + + @After + public void tearDown() throws Exception { + m_server.stopServer(); + File dbFile = new File(m_server.getProperties().getProperty(PERSISTENT_STORE_PROPERTY_NAME)); + if (dbFile.exists()) { + dbFile.delete(); + } + } + + @Test + public void loadAuthenticator() throws Exception { + Properties props = new Properties(IntegrationUtils.prepareTestPropeties()); + props.setProperty(AUTHENTICATOR_CLASS_NAME, "org.eclipse.moquette.server.ConfigurationClassLoaderTest"); + startServer(props); + assertTrue(true); + m_server.stopServer(); + } + + @Test + public void loadAuthorizator() throws Exception { + Properties props = new Properties(IntegrationUtils.prepareTestPropeties()); + props.setProperty(AUTHORIZATOR_CLASS_NAME, "org.eclipse.moquette.server.ConfigurationClassLoaderTest"); + startServer(props); + assertTrue(true); + m_server.stopServer(); + } + + @Override + public boolean checkValid(String username, String password) { + return true; + } + + @Override + public boolean canWrite(String topic, String user, String client) { + return true; + } + + @Override + public boolean canRead(String topic, String user, String client) { + return true; + } + +} diff --git a/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java b/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java index 26ad4759..f5e5701c 100644 --- a/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java +++ b/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java @@ -39,4 +39,7 @@ public class Constants { public static final String KEY_MANAGER_PASSWORD_PROPERTY_NAME = "key_manager_password"; public static final String ALLOW_ANONYMOUS_PROPERTY_NAME = "allow_anonymous"; public static final String ACL_FILE_PROPERTY_NAME = "acl_file"; + public static final String AUTHORIZATOR_CLASS_NAME = "authorizator_class"; + public static final String AUTHENTICATOR_CLASS_NAME = "authenticator_class"; + }