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 7978af5c..89346822 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 @@ -206,7 +206,13 @@ public class SimpleMessaging implements IMessaging, EventHandler { String aclFilePath = props.getProperty(ACL_FILE_PROPERTY_NAME, ""); IAuthorizator authorizator; if (aclFilePath != null && !aclFilePath.isEmpty()) { - authorizator = new ACLAuthorizator(configPath, aclFilePath); + 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(); diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLAuthorizator.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLAuthorizator.java deleted file mode 100644 index 4c438196..00000000 --- a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLAuthorizator.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.spi.impl.security; - -import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.*; -import static org.eclipse.moquette.spi.impl.security.Authorization.Permission; -import org.eclipse.moquette.spi.impl.subscriptions.SubscriptionsStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.text.ParseException; -import java.util.Collections; -import java.util.List; - -/** - * @author andrea - */ -public class ACLAuthorizator implements IAuthorizator { - - private static final Logger LOG = LoggerFactory.getLogger(ACLAuthorizator.class); - - List m_authorisations = Collections.emptyList(); - - public ACLAuthorizator(String parent, String filePath) { - File aclFile = new File(parent, filePath); - try { - m_authorisations = ACLFileParser.parse(aclFile); - } catch (ParseException pex) { - LOG.warn(String.format("Format error in parsing acl file %s", aclFile), pex); - } - } - - public ACLAuthorizator(List authorisations) { - m_authorisations = authorisations; - } - - @Override - public boolean canWrite(String topic) { - return canDoOperation(topic, WRITE); - } - - public boolean canRead(String topic) { - return canDoOperation(topic, READ); - } - - private boolean canDoOperation(String topic, Permission permission) { - for (Authorization auth : m_authorisations) { - if (auth.permission == permission) { - if (SubscriptionsStore.matchTopics(topic, auth.topic)) { - return true; - } - } - } - return false; - } -} diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLFileParser.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLFileParser.java index e05d52a7..fccb94f0 100644 --- a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLFileParser.java +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLFileParser.java @@ -39,21 +39,21 @@ public class ACLFileParser { /** * Parse the configuration from file. */ - public static List parse(File file) throws ParseException { + public static AuthorizationsCollector parse(File file) throws ParseException { if (file == null) { LOG.warn("parsing NULL file, so fallback on default configuration!"); - return Collections.emptyList(); + return AuthorizationsCollector.emptyImmutableCollector(); } if (!file.exists()) { LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath())); - return Collections.emptyList(); + return AuthorizationsCollector.emptyImmutableCollector(); } try { FileReader reader = new FileReader(file); return parse(reader); } catch (FileNotFoundException fex) { LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()), fex); - return Collections.emptyList(); + return AuthorizationsCollector.emptyImmutableCollector(); } } @@ -62,16 +62,17 @@ public class ACLFileParser { * * @throws java.text.ParseException if the format is not compliant. */ - public static List parse(Reader reader) throws ParseException { + public static AuthorizationsCollector parse(Reader reader) throws ParseException { if (reader == null) { //just log and return default properties LOG.warn("parsing NULL reader, so fallback on default configuration!"); - return Collections.emptyList(); + return AuthorizationsCollector.emptyImmutableCollector(); } BufferedReader br = new BufferedReader(reader); String line; - List authorizations = new ArrayList(); + AuthorizationsCollector collector = new AuthorizationsCollector(); + try { while ((line = br.readLine()) != null) { int commentMarker = line.indexOf('#'); @@ -89,34 +90,12 @@ public class ACLFileParser { continue; } - authorizations.add(parseAuthLine(line)); + collector.parse(line); } } } catch (IOException ex) { throw new ParseException("Failed to read", 1); } - return authorizations; - } - - protected static Authorization parseAuthLine(String line) throws ParseException { - String[] tokens = line.split("\\s+"); - String keyword = tokens[0]; - if ("topic".equalsIgnoreCase(keyword)) { - if (tokens.length > 2) { - //if the tokenized lines has 3 token the second must be the permission - try { - Permission permission = Permission.valueOf(tokens[1].toUpperCase()); - //bring topic with all original spacing - String topic = line.substring(line.indexOf(tokens[2])); - - return new Authorization(topic, permission); - } catch (IllegalArgumentException iaex) { - throw new ParseException("invalid permission token", 1); - } - } - String topic = tokens[1]; - return new Authorization(topic); - } - return null; + return collector; } } diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/AuthorizationsCollector.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/AuthorizationsCollector.java new file mode 100644 index 00000000..269ce465 --- /dev/null +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/AuthorizationsCollector.java @@ -0,0 +1,105 @@ +/* + * 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.spi.impl.security; + +import org.eclipse.moquette.spi.impl.subscriptions.SubscriptionsStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.READ; +import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.READWRITE; +import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.WRITE; + +/** + * Used by the ACLFileParser to push all authorizations it finds. + * ACLAuthorizator uses it in read mode to check it topics matches the ACLs. + * + * Not thread safe. + * + * @author andrea + */ +class AuthorizationsCollector implements IAuthorizator { + + private static final Logger LOG = LoggerFactory.getLogger(AuthorizationsCollector.class); + + private List m_authorizations = new ArrayList(); + + static final AuthorizationsCollector emptyImmutableCollector() { + AuthorizationsCollector coll = new AuthorizationsCollector(); + coll.m_authorizations = Collections.emptyList(); + return coll; + } + + void parse(String line) { + try { + m_authorizations.add(parseAuthLine(line)); + } catch (ParseException pex) { + LOG.warn(String.format("Error parsing ACL definition [%s]", line), pex); + } + } + + protected static Authorization parseAuthLine(String line) throws ParseException { + String[] tokens = line.split("\\s+"); + String keyword = tokens[0]; + if ("topic".equalsIgnoreCase(keyword)) { + if (tokens.length > 2) { + //if the tokenized lines has 3 token the second must be the permission + try { + Authorization.Permission permission = Authorization.Permission.valueOf(tokens[1].toUpperCase()); + //bring topic with all original spacing + String topic = line.substring(line.indexOf(tokens[2])); + + return new Authorization(topic, permission); + } catch (IllegalArgumentException iaex) { + throw new ParseException("invalid permission token", 1); + } + } + String topic = tokens[1]; + return new Authorization(topic); + } + return null; + } + + @Override + public boolean canWrite(String topic) { + return canDoOperation(topic, WRITE); + } + + @Override + public boolean canRead(String topic) { + return canDoOperation(topic, READ); + } + + private boolean canDoOperation(String topic, Authorization.Permission permission) { + for (Authorization auth : m_authorizations) { + if (auth.permission == permission || auth.permission == READWRITE) { + if (SubscriptionsStore.matchTopics(topic, auth.topic)) { + return true; + } + } + } + return false; + } + + public boolean isEmpty() { + return m_authorizations.isEmpty(); + } +} diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/DenyAllAuthorizator.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/DenyAllAuthorizator.java new file mode 100644 index 00000000..ff394e3e --- /dev/null +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/DenyAllAuthorizator.java @@ -0,0 +1,31 @@ +/* + * 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.spi.impl.security; + +/** + * @author andrea + */ +public class DenyAllAuthorizator implements IAuthorizator { + @Override + public boolean canWrite(String topic) { + return true; + } + + @Override + public boolean canRead(String topic) { + return true; + } +} \ No newline at end of file diff --git a/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLAuthorizatorTest.java b/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLAuthorizatorTest.java deleted file mode 100644 index ab467d16..00000000 --- a/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLAuthorizatorTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.eclipse.moquette.spi.impl.security; - -import org.junit.Test; - -import static java.util.Arrays.*; -import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.*; -import static org.junit.Assert.*; - -public class ACLAuthorizatorTest { - - @Test - public void testCanWriteSimpleTopic() { - ACLAuthorizator authorizator = new ACLAuthorizator(asList(new Authorization("/sensors", WRITE))); - - //verify - assertTrue(authorizator.canWrite("/sensors")); - } - - @Test - public void testCanReadSimpleTopic() { - ACLAuthorizator authorizator = new ACLAuthorizator(asList(new Authorization("/sensors", READ))); - - //verify - assertTrue(authorizator.canRead("/sensors")); - } - - @Test - public void testCanReadWriteMixedSimpleTopic() { - Authorization topicWriteACL = new Authorization("/sensors", WRITE); - Authorization topicReadACL = new Authorization("/sensors/anemometer", READ); - ACLAuthorizator authorizator = new ACLAuthorizator(asList(topicWriteACL, topicReadACL)); - - //verify - assertTrue(authorizator.canWrite("/sensors")); - assertFalse(authorizator.canRead("/sensors")); - } - - @Test - public void testCanWriteMultiMatherTopic() { - ACLAuthorizator authorizator = new ACLAuthorizator(asList(new Authorization("/sensors/#", WRITE))); - - //verify - assertTrue(authorizator.canWrite("/sensors/anemometer/wind")); - } - - @Test - public void testCanWriteSingleMatherTopic() { - ACLAuthorizator authorizator = new ACLAuthorizator(asList(new Authorization("/sensors/+", WRITE))); - - //verify - assertTrue(authorizator.canWrite("/sensors/anemometer")); - } - -} \ No newline at end of file diff --git a/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLFileParserTest.java b/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLFileParserTest.java index c4f992c4..d82bc374 100644 --- a/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLFileParserTest.java +++ b/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLFileParserTest.java @@ -30,14 +30,10 @@ import static org.junit.Assert.*; * */ public class ACLFileParserTest { - private static final Authorization RW_ANEMOMETER = new Authorization("/weather/italy/anemometer"); - private static final Authorization R_ANEMOMETER = new Authorization("/weather/italy/anemometer", Permission.READ); - private static final Authorization W_ANEMOMETER = new Authorization("/weather/italy/anemometer", Permission.WRITE); - @Test public void testParseEmpty() throws ParseException { Reader conf = new StringReader(" "); - List authorizations = ACLFileParser.parse(conf); + AuthorizationsCollector authorizations = ACLFileParser.parse(conf); //Verify assertTrue(authorizations.isEmpty()); @@ -46,7 +42,7 @@ public class ACLFileParserTest { @Test public void testParseValidComment() throws ParseException { Reader conf = new StringReader("#simple comment"); - List authorizations = ACLFileParser.parse(conf); + AuthorizationsCollector authorizations = ACLFileParser.parse(conf); //Verify assertTrue(authorizations.isEmpty()); @@ -61,58 +57,11 @@ public class ACLFileParserTest { @Test public void testParseSingleLineACL() throws ParseException { Reader conf = new StringReader("topic /weather/italy/anemometer"); - List authorizations = ACLFileParser.parse(conf); + AuthorizationsCollector authorizations = ACLFileParser.parse(conf); //Verify - assertEquals(1, authorizations.size()); - Authorization anemosAuth = authorizations.iterator().next(); - assertEquals(RW_ANEMOMETER, anemosAuth); + assertTrue(authorizations.canRead("/weather/italy/anemometer")); + assertTrue(authorizations.canWrite("/weather/italy/anemometer")); } - @Test - public void testParseAuthLineValid() throws ParseException { - Authorization authorization = ACLFileParser.parseAuthLine("topic /weather/italy/anemometer"); - - //Verify - assertEquals(RW_ANEMOMETER, authorization); - } - - @Test - public void testParseAuthLineValid_read() throws ParseException { - Authorization authorization = ACLFileParser.parseAuthLine("topic read /weather/italy/anemometer"); - - //Verify - assertEquals(R_ANEMOMETER, authorization); - } - - @Test - public void testParseAuthLineValid_write() throws ParseException { - Authorization authorization = ACLFileParser.parseAuthLine("topic write /weather/italy/anemometer"); - - //Verify - assertEquals(W_ANEMOMETER, authorization); - } - - @Test - public void testParseAuthLineValid_readwrite() throws ParseException { - Authorization authorization = ACLFileParser.parseAuthLine("topic readwrite /weather/italy/anemometer"); - - //Verify - assertEquals(RW_ANEMOMETER, authorization); - } - - - @Test - public void testParseAuthLineValid_topic_with_space() throws ParseException { - Authorization expected = new Authorization("/weather/eastern italy/anemometer"); - Authorization authorization = ACLFileParser.parseAuthLine("topic readwrite /weather/eastern italy/anemometer"); - - //Verify - assertEquals(expected, authorization); - } - - @Test(expected = ParseException.class) - public void testParseAuthLineValid_invalid() throws ParseException { - ACLFileParser.parseAuthLine("topic faker /weather/italy/anemometer"); - } } \ No newline at end of file diff --git a/broker/src/test/java/org/eclipse/moquette/spi/impl/security/AuthorizationsCollectorTest.java b/broker/src/test/java/org/eclipse/moquette/spi/impl/security/AuthorizationsCollectorTest.java new file mode 100644 index 00000000..de6f8325 --- /dev/null +++ b/broker/src/test/java/org/eclipse/moquette/spi/impl/security/AuthorizationsCollectorTest.java @@ -0,0 +1,133 @@ +/* + * 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.spi.impl.security; + +import org.junit.Before; +import org.junit.Test; + +import java.text.ParseException; + +import static java.util.Arrays.asList; +import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.READ; +import static org.eclipse.moquette.spi.impl.security.Authorization.Permission.WRITE; +import static org.eclipse.moquette.spi.impl.security.AuthorizationsCollector.*; +import static org.junit.Assert.*; + +/** + * @author andrea + */ +public class AuthorizationsCollectorTest { + + private static final Authorization RW_ANEMOMETER = new Authorization("/weather/italy/anemometer"); + private static final Authorization R_ANEMOMETER = new Authorization("/weather/italy/anemometer", Authorization.Permission.READ); + private static final Authorization W_ANEMOMETER = new Authorization("/weather/italy/anemometer", Authorization.Permission.WRITE); + + private AuthorizationsCollector authorizator; + + @Before + public void setUp() { + authorizator = new AuthorizationsCollector(); + } + + @Test + public void testParseAuthLineValid() throws ParseException { + Authorization authorization = parseAuthLine("topic /weather/italy/anemometer"); + + //Verify + assertEquals(RW_ANEMOMETER, authorization); + } + + @Test + public void testParseAuthLineValid_read() throws ParseException { + Authorization authorization = parseAuthLine("topic read /weather/italy/anemometer"); + + //Verify + assertEquals(R_ANEMOMETER, authorization); + } + + @Test + public void testParseAuthLineValid_write() throws ParseException { + Authorization authorization = parseAuthLine("topic write /weather/italy/anemometer"); + + //Verify + assertEquals(W_ANEMOMETER, authorization); + } + + @Test + public void testParseAuthLineValid_readwrite() throws ParseException { + Authorization authorization = parseAuthLine("topic readwrite /weather/italy/anemometer"); + + //Verify + assertEquals(RW_ANEMOMETER, authorization); + } + + + @Test + public void testParseAuthLineValid_topic_with_space() throws ParseException { + Authorization expected = new Authorization("/weather/eastern italy/anemometer"); + Authorization authorization = parseAuthLine("topic readwrite /weather/eastern italy/anemometer"); + + //Verify + assertEquals(expected, authorization); + } + + @Test(expected = ParseException.class) + public void testParseAuthLineValid_invalid() throws ParseException { + parseAuthLine("topic faker /weather/italy/anemometer"); + } + + @Test + public void testCanWriteSimpleTopic() { + authorizator.parse("topic write /sensors"); + + //verify + assertTrue(authorizator.canWrite("/sensors")); + } + + @Test + public void testCanReadSimpleTopic() { + authorizator.parse("topic read /sensors"); + + //verify + assertTrue(authorizator.canRead("/sensors")); + } + + @Test + public void testCanReadWriteMixedSimpleTopic() { + authorizator.parse("topic write /sensors"); + authorizator.parse("topic read /sensors/anemometer"); + + //verify + assertTrue(authorizator.canWrite("/sensors")); + assertFalse(authorizator.canRead("/sensors")); + } + + @Test + public void testCanWriteMultiMatherTopic() { + authorizator.parse("topic write /sensors/#"); + + //verify + assertTrue(authorizator.canWrite("/sensors/anemometer/wind")); + } + + @Test + public void testCanWriteSingleMatherTopic() { + authorizator.parse("topic write /sensors/+"); + + //verify + assertTrue(authorizator.canWrite("/sensors/anemometer")); + } +} \ No newline at end of file