TSK-339 Make Taskana configurable

This commit is contained in:
BerndBreier 2018-02-22 13:17:36 +01:00 committed by Holger Hagen
parent 633bc2372f
commit f327aee7b5
6 changed files with 320 additions and 1 deletions

View File

@ -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.
*

View File

@ -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<TaskanaRole, Set<String>> 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<String> 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<String> 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.

View File

@ -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;
}
}

View File

@ -71,4 +71,15 @@ public final class LoggerUtils {
return builder.toString();
}
}
public static <T> String setToString(Set<T> set) {
if (set == null || set.isEmpty()) {
return "[]";
}
StringBuilder result = new StringBuilder("[");
set.forEach(e -> result.append("(").append(e).append(") ,"));
result.append("]");
return result.toString();
}
}

View File

@ -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<TaskanaRole> rolesConfigured = super.roleMap.keySet();
assertTrue(rolesConfigured.contains(TaskanaRole.ADMIN));
assertTrue(rolesConfigured.contains(TaskanaRole.BUSINESS_ADMIN));
assertTrue(rolesConfigured.contains(TaskanaRole.USER));
Set<String> users = roleMap.get(TaskanaRole.USER);
assertTrue(users.contains("user_1_1"));
assertTrue(users.contains("user_1_2"));
Set<String> admins = roleMap.get(TaskanaRole.ADMIN);
assertTrue(admins.contains("teamlead_1"));
assertTrue(admins.contains("teamlead_2"));
Set<String> 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<TaskanaRole> rolesConfigured = super.roleMap.keySet();
assertTrue(rolesConfigured.contains(TaskanaRole.ADMIN));
assertTrue(rolesConfigured.contains(TaskanaRole.BUSINESS_ADMIN));
assertTrue(rolesConfigured.contains(TaskanaRole.USER));
Set<String> users = roleMap.get(TaskanaRole.USER);
assertTrue(users.contains("nobody"));
Set<String> admins = roleMap.get(TaskanaRole.ADMIN);
assertTrue(admins.contains("holger"));
assertTrue(admins.contains("stefan"));
Set<String> 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<TaskanaRole> rolesConfigured = super.roleMap.keySet();
assertTrue(rolesConfigured.contains(TaskanaRole.ADMIN));
assertTrue(rolesConfigured.contains(TaskanaRole.BUSINESS_ADMIN));
assertTrue(rolesConfigured.contains(TaskanaRole.USER));
Set<String> users = roleMap.get(TaskanaRole.USER);
assertTrue(users.isEmpty());
Set<String> admins = roleMap.get(TaskanaRole.ADMIN);
assertTrue(admins.contains("holger"));
assertTrue(admins.contains("name=stefan,organisation=novatec"));
Set<String> 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;
}
}

View File

@ -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