From 6d3fbc4591002413e23130da4de17ec1b177a2b4 Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 17 May 2015 12:34:46 +0200 Subject: [PATCH] Implementd basic parsing fro ACL lines --- ChangeLog.txt | 2 +- .../spi/impl/security/ACLFileParser.java | 99 +++++++++++++++ .../spi/impl/security/Authorization.java | 62 +++++++++ .../spi/impl/security/ACLFileParserTest.java | 118 ++++++++++++++++++ 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLFileParser.java create mode 100644 broker/src/main/java/org/eclipse/moquette/spi/impl/security/Authorization.java create mode 100644 broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLFileParserTest.java diff --git a/ChangeLog.txt b/ChangeLog.txt index fca744bf..2e0dea53 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -11,7 +11,7 @@ Version 0.7: Removed OSGi bundle, developer time burner Fixed github issue #30, returning error on mapdb file not deleted that corrupt reiterated builds Added handling of allow_anonymous clients (as in mosquitto issue #34) - Modified the password_file format to use password soterd in sha256 and not plain text + Modified the password_file format to use password stored in sha256 and not plain text Version 0.6: Fix issue #42, bad password file path composition 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 new file mode 100644 index 00000000..f8b460aa --- /dev/null +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/ACLFileParser.java @@ -0,0 +1,99 @@ +/* + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +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; + +/** + * @author andrea + * */ +public class ACLFileParser { + private static final Logger LOG = LoggerFactory.getLogger(ACLFileParser.class); + + /** + * Parse the ACL configuration file + * + * @throws java.text.ParseException if the format is not compliant. + */ + public static List 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(); + } + + BufferedReader br = new BufferedReader(reader); + String line; + List authorizations = new ArrayList(); + try { + while ((line = br.readLine()) != null) { + int commentMarker = line.indexOf('#'); + if (commentMarker != -1) { + if (commentMarker == 0) { + //skip its a comment + continue; + } else { + //it's a malformed comment + throw new ParseException(line, commentMarker); + } + } else { + if (line.isEmpty() || line.matches("^\\s*$")) { + //skip it's a black line + continue; + } + + authorizations.add(parseAuthLine(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; + } +} diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/security/Authorization.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/Authorization.java new file mode 100644 index 00000000..a4969d15 --- /dev/null +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/security/Authorization.java @@ -0,0 +1,62 @@ +/* + * 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; + +/** + * Carries the read/write authorization to topics for the users. + * + * @author andrea + */ +class Authorization { + private String topic; + private Permission permission; + + /** + * Access rights + * */ + enum Permission { + READ, WRITE, READWRITE + }; + + Authorization(String topic) { + this(topic, Permission.READWRITE); + } + + Authorization(String topic, Permission permission) { + this.topic = topic; + this.permission = permission; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Authorization that = (Authorization) o; + + if (permission != that.permission) return false; + if (!topic.equals(that.topic)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = topic.hashCode(); + result = 31 * result + permission.hashCode(); + return result; + } +} 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 new file mode 100644 index 00000000..c4f992c4 --- /dev/null +++ b/broker/src/test/java/org/eclipse/moquette/spi/impl/security/ACLFileParserTest.java @@ -0,0 +1,118 @@ +/* + * 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.Test; + +import java.io.Reader; +import java.io.StringReader; +import java.text.ParseException; +import java.util.List; +import static org.eclipse.moquette.spi.impl.security.Authorization.*; + +import static org.junit.Assert.*; + +/** + * @author andrea + * */ +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); + + //Verify + assertTrue(authorizations.isEmpty()); + } + + @Test + public void testParseValidComment() throws ParseException { + Reader conf = new StringReader("#simple comment"); + List authorizations = ACLFileParser.parse(conf); + + //Verify + assertTrue(authorizations.isEmpty()); + } + + @Test(expected = ParseException.class) + public void testParseInvalidComment() throws ParseException { + Reader conf = new StringReader(" #simple comment"); + ACLFileParser.parse(conf); + } + + @Test + public void testParseSingleLineACL() throws ParseException { + Reader conf = new StringReader("topic /weather/italy/anemometer"); + List authorizations = ACLFileParser.parse(conf); + + //Verify + assertEquals(1, authorizations.size()); + Authorization anemosAuth = authorizations.iterator().next(); + assertEquals(RW_ANEMOMETER, anemosAuth); + } + + @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