From f327aee7b558f8b885ac2365c8603258d107bbff Mon Sep 17 00:00:00 2001 From: BerndBreier <33351391+BerndBreier@users.noreply.github.com> Date: Thu, 22 Feb 2018 13:17:36 +0100 Subject: [PATCH] TSK-339 Make Taskana configurable --- .../TaskanaEngineConfiguration.java | 36 ++++- .../pro/taskana/impl/TaskanaEngineImpl.java | 93 +++++++++++ .../java/pro/taskana/impl/TaskanaRole.java | 32 ++++ .../pro/taskana/impl/util/LoggerUtils.java | 11 ++ .../config/TaskanaRoleConfigAccTest.java | 146 ++++++++++++++++++ .../test/resources/taskanaroles.properties | 3 + 6 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaRole.java create mode 100644 lib/taskana-core/src/test/java/acceptance/config/TaskanaRoleConfigAccTest.java create mode 100644 lib/taskana-core/src/test/resources/taskanaroles.properties diff --git a/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java b/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java index 8769b69af..b8361693b 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java +++ b/lib/taskana-core/src/main/java/pro/taskana/configuration/TaskanaEngineConfiguration.java @@ -23,8 +23,13 @@ public class TaskanaEngineConfiguration { private static final String USER_PASSWORD = "sa"; private static final String JDBC_H2_MEM_TASKANA = "jdbc:h2:mem:taskana;IGNORECASE=TRUE"; private static final String H2_DRIVER = "org.h2.Driver"; + private static final String TASKANA_ROLES_PROPERTIES = "/taskanaroles.properties"; + private static final String TASKANA_PROPERTIES_SEPARATOR = "|"; + protected DataSource dataSource; protected DbSchemaCreator dbScriptRunner; + protected String propertiesFileName = TASKANA_ROLES_PROPERTIES; + protected String propertiesSeparator = TASKANA_PROPERTIES_SEPARATOR; // global switch to enable JAAS based authentication and Taskana // authorizations @@ -42,7 +47,21 @@ public class TaskanaEngineConfiguration { public TaskanaEngineConfiguration(DataSource dataSource, boolean useManagedTransactions, boolean securityEnabled) throws SQLException { + this(dataSource, useManagedTransactions, securityEnabled, null, null); + } + + public TaskanaEngineConfiguration(DataSource dataSource, boolean useManagedTransactions, + boolean securityEnabled, String propertiesFileName, String propertiesSeparator) throws SQLException { this.useManagedTransactions = useManagedTransactions; + this.securityEnabled = securityEnabled; + + if (propertiesFileName != null) { + this.propertiesFileName = propertiesFileName; + } + + if (propertiesSeparator != null) { + this.propertiesSeparator = propertiesSeparator; + } if (dataSource != null) { this.dataSource = dataSource; @@ -53,7 +72,6 @@ public class TaskanaEngineConfiguration { dbScriptRunner = new DbSchemaCreator(this.dataSource); dbScriptRunner.run(); - this.securityEnabled = securityEnabled; } public static DataSource createDefaultDataSource() { @@ -100,6 +118,22 @@ public class TaskanaEngineConfiguration { return this.useManagedTransactions; } + public String getPropertiesFileName() { + return this.propertiesFileName; + } + + public void setPropertiesFileName(String propertiesFileName) { + this.propertiesFileName = propertiesFileName; + } + + public String getPropertiesSeparator() { + return this.propertiesSeparator; + } + + public void setPropertiesSeparator(String propertiesSeparator) { + this.propertiesSeparator = propertiesSeparator; + } + /** * Helper method to determine whether all access ids (user Id and group ids) should be used in lower case. * diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java index 66c5ed676..abcf52890 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaEngineImpl.java @@ -1,9 +1,22 @@ package pro.taskana.impl; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; @@ -28,6 +41,7 @@ import pro.taskana.exceptions.ConnectionNotSetException; import pro.taskana.exceptions.SystemException; import pro.taskana.exceptions.UnsupportedDatabaseException; import pro.taskana.impl.persistence.MapTypeHandler; +import pro.taskana.impl.util.LoggerUtils; import pro.taskana.mappings.AttachmentMapper; import pro.taskana.mappings.ClassificationMapper; import pro.taskana.mappings.DistributionTargetMapper; @@ -52,6 +66,7 @@ public class TaskanaEngineImpl implements TaskanaEngine { protected SqlSessionFactory sessionFactory; protected ConnectionManagementMode mode = ConnectionManagementMode.PARTICIPATE; protected java.sql.Connection connection = null; + protected Map> roleMap = new HashMap<>(); public static TaskanaEngine createTaskanaEngine(TaskanaEngineConfiguration taskanaEngineConfiguration) { return new TaskanaEngineImpl(taskanaEngineConfiguration); @@ -61,6 +76,8 @@ public class TaskanaEngineImpl implements TaskanaEngine { this.taskanaEngineConfiguration = taskanaEngineConfiguration; createTransactionFactory(taskanaEngineConfiguration.getUseManagedTransactions()); this.sessionManager = createSqlSessionManager(); + initRoles(taskanaEngineConfiguration.getPropertiesFileName(), + taskanaEngineConfiguration.getPropertiesSeparator()); } @Override @@ -275,6 +292,82 @@ public class TaskanaEngineImpl implements TaskanaEngine { } } + protected void initRoles(String propertiesFilename, String propertiesSeparator) { + String propertiesFile = propertiesFilename == null ? taskanaEngineConfiguration.getPropertiesFileName() + : propertiesFilename; + String separator = propertiesSeparator == null ? taskanaEngineConfiguration.getPropertiesSeparator() + : propertiesSeparator; + if (taskanaEngineConfiguration.isSecurityEnabled()) { + // is the filename fully qualified or relative? + boolean loadFromClasspath = true; + File f = new File(propertiesFile); + if (f.exists() && !f.isDirectory()) { + loadFromClasspath = false; + } + + Properties props = new Properties(); + List validPropertyNames = Arrays.asList(TaskanaRole.USER.getPropertyName(), + TaskanaRole.BUSINESS_ADMIN.getPropertyName(), TaskanaRole.ADMIN.getPropertyName()); + try { + if (loadFromClasspath) { + + InputStream inputStream = this.getClass().getResourceAsStream(propertiesFile); + if (inputStream == null) { + LOGGER.error("properties file {} was not found on classpath.", propertiesFile); + ensureRoleMapIsFullyInitialized(); + return; + } else { + props.load(new InputStreamReader(inputStream)); + } + } else { + props.load(new FileInputStream(propertiesFile)); + } + for (Object obj : props.keySet()) { + String propertyName = ((String) obj); + if (validPropertyNames.contains(propertyName.toLowerCase().trim())) { + String propertyValue = props.getProperty(propertyName); + Set roleMemberSet = new HashSet<>(); + StringTokenizer st = new StringTokenizer(propertyValue, separator); + while (st.hasMoreTokens()) { + String token = st.nextToken().toLowerCase().trim(); + roleMemberSet.add(token); + } + TaskanaRole key = TaskanaRole.fromProperyName(propertyName); + if (key != null) { + roleMap.put(key, roleMemberSet); + } else { + LOGGER.error("internal System error when processing properties file {}.", propertiesFile); + throw new SystemException( + "internal System error when processing properties file " + propertiesFile); + } + } + } + ensureRoleMapIsFullyInitialized(); + + roleMap.forEach( + (k, v) -> LOGGER.debug("Found Taskana RoleConfig {} : {} ", k, LoggerUtils.setToString(v))); + + } catch (IOException e) { + LOGGER.error("caught IOException when processing properties file {}.", propertiesFile); + throw new SystemException("internal System error when processing properties file " + propertiesFile); + } + } + } + + private void ensureRoleMapIsFullyInitialized() { + // make sure that roleMap does not return null for any role + if (!roleMap.containsKey(TaskanaRole.ADMIN)) { + roleMap.put(TaskanaRole.ADMIN, new HashSet<>()); + } + if (!roleMap.containsKey(TaskanaRole.BUSINESS_ADMIN)) { + roleMap.put(TaskanaRole.BUSINESS_ADMIN, new HashSet<>()); + } + + if (!roleMap.containsKey(TaskanaRole.USER)) { + roleMap.put(TaskanaRole.USER, new HashSet<>()); + } + } + /** * With sessionStack, we maintain a Stack of SqlSessionManager objects on a per thread basis. SqlSessionManager is * the MyBatis object that wraps database connections. The purpose of this stack is to keep track of nested calls. diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaRole.java b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaRole.java new file mode 100644 index 000000000..d80433f42 --- /dev/null +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/TaskanaRole.java @@ -0,0 +1,32 @@ +package pro.taskana.impl; + +/** + * This enum contains all roles that are known to taskana. + */ +public enum TaskanaRole { + USER("taskana.roles.user"), + BUSINESS_ADMIN("taskana.roles.businessadmin"), + ADMIN("taskana.roles.admin"); + + private final String propertyName; + + TaskanaRole(String propertyName) { + this.propertyName = propertyName; + } + + public static TaskanaRole fromProperyName(String name) { + if (USER.propertyName.equalsIgnoreCase(name)) { + return TaskanaRole.USER; + } else if (BUSINESS_ADMIN.propertyName.equalsIgnoreCase(name)) { + return TaskanaRole.BUSINESS_ADMIN; + } else if (ADMIN.propertyName.equalsIgnoreCase(name)) { + return TaskanaRole.ADMIN; + } else { + return null; + } + } + + public String getPropertyName() { + return propertyName; + } +} diff --git a/lib/taskana-core/src/main/java/pro/taskana/impl/util/LoggerUtils.java b/lib/taskana-core/src/main/java/pro/taskana/impl/util/LoggerUtils.java index df1881265..7adf4867d 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/impl/util/LoggerUtils.java +++ b/lib/taskana-core/src/main/java/pro/taskana/impl/util/LoggerUtils.java @@ -71,4 +71,15 @@ public final class LoggerUtils { return builder.toString(); } } + + public static String setToString(Set set) { + if (set == null || set.isEmpty()) { + return "[]"; + } + + StringBuilder result = new StringBuilder("["); + set.forEach(e -> result.append("(").append(e).append(") ,")); + result.append("]"); + return result.toString(); + } } diff --git a/lib/taskana-core/src/test/java/acceptance/config/TaskanaRoleConfigAccTest.java b/lib/taskana-core/src/test/java/acceptance/config/TaskanaRoleConfigAccTest.java new file mode 100644 index 000000000..4369ef5dc --- /dev/null +++ b/lib/taskana-core/src/test/java/acceptance/config/TaskanaRoleConfigAccTest.java @@ -0,0 +1,146 @@ +package acceptance.config; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.sql.SQLException; +import java.util.Set; + +import org.h2.store.fs.FileUtils; +import org.junit.Test; + +import pro.taskana.configuration.TaskanaEngineConfiguration; +import pro.taskana.impl.TaskanaEngineImpl; +import pro.taskana.impl.TaskanaRole; +import pro.taskana.impl.configuration.TaskanaEngineConfigurationTest; + +/** + * Test taskana's role configuration. + * + * @author bbr + */ +public class TaskanaRoleConfigAccTest extends TaskanaEngineImpl { + + public TaskanaRoleConfigAccTest() throws SQLException { + super(new TaskanaEngineConfiguration(TaskanaEngineConfigurationTest.getDataSource(), true)); + } + + @Test + public void testStandardConfig() { + Set rolesConfigured = super.roleMap.keySet(); + assertTrue(rolesConfigured.contains(TaskanaRole.ADMIN)); + assertTrue(rolesConfigured.contains(TaskanaRole.BUSINESS_ADMIN)); + assertTrue(rolesConfigured.contains(TaskanaRole.USER)); + + Set users = roleMap.get(TaskanaRole.USER); + assertTrue(users.contains("user_1_1")); + assertTrue(users.contains("user_1_2")); + + Set admins = roleMap.get(TaskanaRole.ADMIN); + assertTrue(admins.contains("teamlead_1")); + assertTrue(admins.contains("teamlead_2")); + + Set businessAdmins = roleMap.get(TaskanaRole.BUSINESS_ADMIN); + assertTrue(businessAdmins.contains("max")); + assertTrue(businessAdmins.contains("moritz")); + + } + + @Test + public void testOtherConfigFileSameDelimiter() throws IOException, SQLException { + String propertiesFileName = createNewConfigFileWithSameDelimiter("/dummyTestConfig.properties"); + try { + initRoles(propertiesFileName, null); + + Set rolesConfigured = super.roleMap.keySet(); + assertTrue(rolesConfigured.contains(TaskanaRole.ADMIN)); + assertTrue(rolesConfigured.contains(TaskanaRole.BUSINESS_ADMIN)); + assertTrue(rolesConfigured.contains(TaskanaRole.USER)); + + Set users = roleMap.get(TaskanaRole.USER); + assertTrue(users.contains("nobody")); + + Set admins = roleMap.get(TaskanaRole.ADMIN); + assertTrue(admins.contains("holger")); + assertTrue(admins.contains("stefan")); + + Set businessAdmins = roleMap.get(TaskanaRole.BUSINESS_ADMIN); + assertTrue(businessAdmins.contains("ebe")); + assertTrue(businessAdmins.contains("konstantin")); + } finally { + deleteFile(propertiesFileName); + } + + } + + @Test + public void testOtherConfigFileDifferentDelimiter() throws IOException, SQLException { + String delimiter = ";"; + String propertiesFileName = createNewConfigFileWithDifferentDelimiter("/dummyTestConfig.properties", delimiter); + try { + initRoles(propertiesFileName, delimiter); + + Set rolesConfigured = super.roleMap.keySet(); + assertTrue(rolesConfigured.contains(TaskanaRole.ADMIN)); + assertTrue(rolesConfigured.contains(TaskanaRole.BUSINESS_ADMIN)); + assertTrue(rolesConfigured.contains(TaskanaRole.USER)); + + Set users = roleMap.get(TaskanaRole.USER); + assertTrue(users.isEmpty()); + + Set admins = roleMap.get(TaskanaRole.ADMIN); + assertTrue(admins.contains("holger")); + assertTrue(admins.contains("name=stefan,organisation=novatec")); + + Set businessAdmins = roleMap.get(TaskanaRole.BUSINESS_ADMIN); + assertTrue(businessAdmins.contains("name=ebe, ou = bpm")); + assertTrue(businessAdmins.contains("konstantin")); + } finally { + deleteFile(propertiesFileName); + } + + } + + private String createNewConfigFileWithDifferentDelimiter(String filename, String delimiter) throws IOException { + String userHomeDirectroy = System.getProperty("user.home"); + String propertiesFileName = userHomeDirectroy + filename; + File f = new File(propertiesFileName); + if (!f.exists()) { + try (PrintWriter writer = new PrintWriter(propertiesFileName, "UTF-8")) { + writer.println("taskana.roles.Admin =hOlGeR " + delimiter + "name=Stefan,Organisation=novatec"); + writer.println(" taskana.roles.businessadmin = name=ebe, ou = bpm " + delimiter + " konstantin "); + writer.println(" taskana.roles.user = "); + } catch (IOException e) { + throw e; + } + } + return propertiesFileName; + } + + private void deleteFile(String propertiesFileName) { + System.out.println("about to delete " + propertiesFileName); + File f = new File(propertiesFileName); + if (f.exists() && !f.isDirectory()) { + FileUtils.delete(propertiesFileName); + } + } + + private String createNewConfigFileWithSameDelimiter(String filename) throws IOException { + String userHomeDirectroy = System.getProperty("user.home"); + String propertiesFileName = userHomeDirectroy + filename; + File f = new File(propertiesFileName); + if (!f.exists()) { + try (PrintWriter writer = new PrintWriter(propertiesFileName, "UTF-8")) { + writer.println("taskana.roles.Admin =hOlGeR|Stefan"); + writer.println(" taskana.roles.businessadmin = ebe | konstantin "); + writer.println(" taskana.roles.user = nobody"); + } catch (IOException e) { + throw e; + } + } + return propertiesFileName; + } + +} diff --git a/lib/taskana-core/src/test/resources/taskanaroles.properties b/lib/taskana-core/src/test/resources/taskanaroles.properties new file mode 100644 index 000000000..700a7da33 --- /dev/null +++ b/lib/taskana-core/src/test/resources/taskanaroles.properties @@ -0,0 +1,3 @@ +taskana.roles.user = group1 | group2|teamlead_1 |teamlead_2 |user_1_1| user_1_1| user_1_2| user_2_1| user_2_2| max|elena|simone +taskana.roles.Admin=teamlead_1|teamlead_2 +taskana.roles.businessadmin=max|Moritz