TSK-1218: Prevent unsecure access to TASKANA

This commit is contained in:
Jörg Heffner 2020-04-28 15:34:18 +02:00
parent 56910acda0
commit 35e7db4355
14 changed files with 285 additions and 19 deletions

View File

@ -35,9 +35,11 @@ import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.TaskanaEngineImpl;
import pro.taskana.common.internal.configuration.DB;
import pro.taskana.common.internal.configuration.DbSchemaCreator;
import pro.taskana.common.internal.configuration.SecurityVerifier;
/**
* This central class creates the TaskanaEngine and holds all the information about DB and Security.
* This central class creates the TaskanaEngine and holds all the information about DB and
* Security.
* <br>
* Security is enabled by default.
*/
@ -86,6 +88,7 @@ public class TaskanaEngineConfiguration {
// global switch to enable JAAS based authentication and Taskana
// authorizations
protected boolean securityEnabled;
protected SecurityVerifier securityVerifier;
protected boolean useManagedTransactions;
// List of configured domain names
protected List<String> domains = new ArrayList<>();
@ -156,6 +159,9 @@ public class TaskanaEngineConfiguration {
"The Database Schema Version doesn't match the expected version "
+ TASKANA_SCHEMA_VERSION);
}
securityVerifier = new SecurityVerifier(this.dataSource, this.getSchemaName());
securityVerifier.checkSecureAccess(securityEnabled);
}
public void initTaskanaProperties(String propertiesFile, String rolesSeparator) {
@ -290,7 +296,7 @@ public class TaskanaEngineConfiguration {
public Map<String, List<String>> getClassificationCategoriesByTypeMap() {
return this.classificationCategoriesByTypeMap.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, e -> new ArrayList<>(e.getValue())));
.collect(Collectors.toMap(Entry::getKey, e -> new ArrayList<>(e.getValue())));
}
public List<String> getClassificationCategoriesByType(String type) {
@ -416,7 +422,7 @@ public class TaskanaEngineConfiguration {
String taskCleanupJobAllCompletedSameParentBusinessProperty =
props.getProperty(TASKANA_JOB_TASK_CLEANUP_ALL_COMPLETED_SAME_PARENTE_BUSINESS);
if (taskCleanupJobAllCompletedSameParentBusinessProperty != null
&& !taskCleanupJobAllCompletedSameParentBusinessProperty.isEmpty()) {
&& !taskCleanupJobAllCompletedSameParentBusinessProperty.isEmpty()) {
try {
taskCleanupJobAllCompletedSameParentBusiness =
Boolean.parseBoolean(taskCleanupJobAllCompletedSameParentBusinessProperty);
@ -522,7 +528,8 @@ public class TaskanaEngineConfiguration {
validPropertyName ->
roleMap.put(
TaskanaRole.fromPropertyName(validPropertyName),
getTokensWithCollection(props.getProperty(validPropertyName), rolesSeparator)));
getTokensWithCollection(props.getProperty(validPropertyName),
rolesSeparator)));
ensureRoleMapIsFullyInitialized();
@ -535,8 +542,8 @@ public class TaskanaEngineConfiguration {
private HashSet<String> getTokensWithCollection(String str, String rolesSeparator) {
return Collections.list(new StringTokenizer(str, rolesSeparator)).stream()
.map(token -> String.valueOf(token).toLowerCase().trim())
.collect(Collectors.toCollection(HashSet::new));
.map(token -> String.valueOf(token).toLowerCase().trim())
.collect(Collectors.toCollection(HashSet::new));
}
private Properties readPropertiesFromFile(String propertiesFile) {
@ -550,7 +557,8 @@ public class TaskanaEngineConfiguration {
LOGGER.error("taskana properties file {} was not found on classpath.", propertiesFile);
} else {
props.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
LOGGER.debug("Role properties were loaded from file {} from classpath.", propertiesFile);
LOGGER
.debug("Role properties were loaded from file {} from classpath.", propertiesFile);
}
} else {
props.load(new FileInputStream(propertiesFile));
@ -559,7 +567,8 @@ public class TaskanaEngineConfiguration {
} catch (IOException e) {
LOGGER.error("caught IOException when processing properties file {}.", propertiesFile);
throw new SystemException(
"internal System error when processing properties file " + propertiesFile, e.getCause());
"internal System error when processing properties file " + propertiesFile,
e.getCause());
}
return props;
}
@ -575,6 +584,7 @@ public class TaskanaEngineConfiguration {
private void ensureRoleMapIsFullyInitialized() {
// make sure that roleMap does not return null for any role
Arrays.stream(TaskanaRole.values()).forEach(role -> roleMap.putIfAbsent(role, new HashSet<>()));
Arrays.stream(TaskanaRole.values())
.forEach(role -> roleMap.putIfAbsent(role, new HashSet<>()));
}
}

View File

@ -0,0 +1,97 @@
package pro.taskana.common.internal.configuration;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.jdbc.SqlRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.exceptions.SystemException;
public class SecurityVerifier {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityVerifier.class);
private static final String SECURITY_FLAG_COLUMN_NAME = "ENFORCE_SECURITY";
private static final String INSERT_SECURITY_FLAG = "INSERT INTO %s.CONFIGURATION VALUES (%b)";
private static final String SELECT_SECURITY_FLAG = "SELECT %s FROM %s.CONFIGURATION";
private final String schemaName;
private DataSource dataSource;
public SecurityVerifier(DataSource dataSource, String schema) {
super();
this.dataSource = dataSource;
this.schemaName = schema;
}
public void checkSecureAccess(boolean securityEnabled) {
if (!securityEnabled) {
LOGGER.debug("Trying to connect in disabled security-mode");
try (Connection connection = dataSource.getConnection()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(connection.getMetaData().toString());
}
SqlRunner sqlRunner = new SqlRunner(connection);
String querySecurity = String.format(SELECT_SECURITY_FLAG, SECURITY_FLAG_COLUMN_NAME,
schemaName);
if ((boolean) sqlRunner.selectOne(querySecurity).get(SECURITY_FLAG_COLUMN_NAME)) {
throw new SystemException(
"Secured TASKANA mode is enforced, can't start in unsecured mode");
}
} catch (SQLException ex) {
LOGGER.info(String.format(
"Security-mode is not yet set. Setting security flag to %b", securityEnabled));
setInitialSecurityMode(securityEnabled);
}
}
LOGGER.debug("Security-mode is enabled");
}
private void setInitialSecurityMode(boolean securityEnabled) {
try (Connection connection = dataSource.getConnection()) {
String setSecurityFlagSql = String.format(INSERT_SECURITY_FLAG,
schemaName, securityEnabled);
try (PreparedStatement preparedStatement = connection.prepareStatement(setSecurityFlagSql)) {
preparedStatement.execute();
if (!connection.getAutoCommit()) {
connection.commit();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Successfully set security-mode to %b", securityEnabled));
}
} catch (SQLException ex) {
LOGGER.error(
"Caught exception while trying to set the initial TASKANA security mode. "
+ "Aborting start-up process!",
ex);
throw new SystemException(
"Couldn't set initial TASKANA security mode. Aborting start-up process!");
}
} catch (SQLException ex) {
LOGGER.error("Caught exception while trying to retrieve connection from datasource ", ex);
}
}
}

View File

@ -177,6 +177,10 @@ CREATE TABLE ATTACHMENT(
CONSTRAINT ATT_CLASS FOREIGN KEY (CLASSIFICATION_ID) REFERENCES CLASSIFICATION ON DELETE NO ACTION
);
CREATE TABLE CONFIGURATION (
ENFORCE_SECURITY BOOLEAN NOT NULL
);
CREATE TABLE TASK_COMMENT(
ID VARCHAR(40) NOT NULL,
TASK_ID VARCHAR(40) NOT NULL,

View File

@ -50,3 +50,7 @@ REORG TABLE TASK_COMMENT;
ALTER TABLE TASK_COMMENT ALTER COLUMN TASK_ID SET DATA TYPE VARCHAR(40);
REORG TABLE TASK_COMMENT;
CREATE TABLE CONFIGURATION (
ENFORCE_SECURITY BOOLEAN NOT NULL
);

View File

@ -238,6 +238,10 @@ CREATE TABLE HISTORY_EVENTS
PRIMARY KEY (ID)
);
CREATE TABLE CONFIGURATION (
ENFORCE_SECURITY BOOLEAN NOT NULL
);
CREATE SEQUENCE SCHEDULED_JOB_SEQ
MINVALUE 1
START WITH 1

View File

@ -35,3 +35,7 @@ ALTER TABLE ATTACHMENT ALTER COLUMN CLASSIFICATION_ID VARCHAR(40);
ALTER TABLE TASK_COMMENT ALTER COLUMN ID VARCHAR(40);
ALTER TABLE TASK_COMMENT ALTER COLUMN TASK_ID VARCHAR(40);
CREATE TABLE CONFIGURATION (
ENFORCE_SECURITY BOOLEAN NOT NULL
);

View File

@ -233,6 +233,10 @@ CREATE TABLE HISTORY_EVENTS
PRIMARY KEY (ID)
);
CREATE TABLE CONFIGURATION (
ENFORCE_SECURITY BOOLEAN NOT NULL
);
CREATE SEQUENCE SCHEDULED_JOB_SEQ
MINVALUE 1
START WITH 1

View File

@ -35,3 +35,7 @@ ALTER TABLE ATTACHMENT ALTER COLUMN CLASSIFICATION_ID TYPE VARCHAR(40);
ALTER TABLE TASK_COMMENT ALTER COLUMN ID TYPE VARCHAR(40);
ALTER TABLE TASK_COMMENT ALTER COLUMN TASK_ID TYPE VARCHAR(40);
CREATE TABLE CONFIGURATION (
ENFORCE_SECURITY BOOLEAN NOT NULL
);

View File

@ -0,0 +1,134 @@
package acceptance.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import pro.taskana.TaskanaEngineConfiguration;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.TaskanaEngineTestConfiguration;
import pro.taskana.common.internal.configuration.DbSchemaCreator;
import pro.taskana.sampledata.SampleDataGenerator;
public class TaskanaSecurityConfigAccTest {
@BeforeAll
public static void setupTests() throws SQLException {
DataSource dataSource = TaskanaEngineTestConfiguration.getDataSource();
String schemaName = TaskanaEngineTestConfiguration.getSchemaName();
DbSchemaCreator dbSchemaCreator = new DbSchemaCreator(dataSource, schemaName);
dbSchemaCreator.run();
}
@AfterEach
public void cleanDb() {
DataSource dataSource = TaskanaEngineTestConfiguration.getDataSource();
String schemaName = TaskanaEngineTestConfiguration.getSchemaName();
SampleDataGenerator sampleDataGenerator = new SampleDataGenerator(dataSource, schemaName);
sampleDataGenerator.clearDb();
}
@Test
public void should_ThrowException_When_CreatingUnsecuredEngineConfigWhileSecurityIsEnforced()
throws SQLException {
setSecurityFlag(true);
ThrowingCallable createUnsecuredTaskanaEngineConfiguration = () -> {
TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(
TaskanaEngineTestConfiguration.getDataSource(), false, false,
TaskanaEngineTestConfiguration.getSchemaName());
};
assertThatThrownBy(createUnsecuredTaskanaEngineConfiguration)
.isInstanceOf(SystemException.class)
.hasMessageContaining("Secured TASKANA mode is enforced, can't start in unsecured mode");
}
@Test
public void should_StartUpNormally_When_CreatingUnsecuredEngineConfigWhileSecurityIsNotEnforced()
throws SQLException {
setSecurityFlag(false);
ThrowingCallable createUnsecuredTaskanaEngineConfiguration = () -> {
TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(
TaskanaEngineTestConfiguration.getDataSource(), false, false,
TaskanaEngineTestConfiguration.getSchemaName());
};
assertThatCode(createUnsecuredTaskanaEngineConfiguration).doesNotThrowAnyException();
}
@Test
public void should_SetSecurityFlag_When_SecurityFlagIsNotSet() throws SQLException {
assertThat(retrieveSecurityFlag()).isNull();
ThrowingCallable createUnsecuredTaskanaEngineConfiguration = () -> {
TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(
TaskanaEngineTestConfiguration.getDataSource(), false, false,
TaskanaEngineTestConfiguration.getSchemaName());
};
assertThatCode(createUnsecuredTaskanaEngineConfiguration).doesNotThrowAnyException();
assertThat(retrieveSecurityFlag()).isFalse();
}
private Boolean retrieveSecurityFlag() throws SQLException {
try (Connection connection = TaskanaEngineTestConfiguration.getDataSource().getConnection()) {
String selectSecurityFlagSql = String
.format("SELECT * FROM %s.CONFIGURATION",
TaskanaEngineTestConfiguration.getSchemaName());
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(selectSecurityFlagSql);
if (resultSet.next()) {
return resultSet.getBoolean(1);
}
statement.close();
return null;
}
}
private void setSecurityFlag(boolean securityFlag) throws SQLException {
try (Connection connection = TaskanaEngineTestConfiguration.getDataSource().getConnection()) {
String sql = String
.format("INSERT INTO %s.CONFIGURATION VALUES (%b)",
TaskanaEngineTestConfiguration.getSchemaName(), securityFlag);
Statement statement = connection.createStatement();
statement.execute(sql);
if (!connection.getAutoCommit()) {
connection.commit();
}
statement.close();
}
}
}

View File

@ -63,7 +63,7 @@ class ClassificationServiceImplIntAutoCommitTest {
@Test
void testFindAllClassifications()
throws ClassificationAlreadyExistException, NotAuthorizedException, DomainNotFoundException,
InvalidArgumentException {
InvalidArgumentException {
Classification classification0 = classificationService.newClassification("TEST1", "", "TASK");
classificationService.createClassification(classification0);
Classification classification1 = classificationService.newClassification("TEST2", "", "TASK");
@ -78,8 +78,8 @@ class ClassificationServiceImplIntAutoCommitTest {
@Test
void testModifiedClassification()
throws ClassificationAlreadyExistException, ClassificationNotFoundException,
NotAuthorizedException, ConcurrencyException, DomainNotFoundException,
InvalidArgumentException {
NotAuthorizedException, ConcurrencyException, DomainNotFoundException,
InvalidArgumentException {
final String description = "TEST SOMETHING";
Classification classification =
classificationService.newClassification("TEST434", "DOMAIN_A", "TASK");
@ -97,7 +97,7 @@ class ClassificationServiceImplIntAutoCommitTest {
@Test
void testInsertClassification()
throws NotAuthorizedException, ClassificationAlreadyExistException, InvalidArgumentException,
DomainNotFoundException {
DomainNotFoundException {
Classification classification =
classificationService.newClassification("TEST1333", "DOMAIN_A", "TASK");
classificationService.createClassification(classification);
@ -115,8 +115,8 @@ class ClassificationServiceImplIntAutoCommitTest {
@Test
void testUpdateClassification()
throws NotAuthorizedException, ClassificationAlreadyExistException,
ClassificationNotFoundException, ConcurrencyException, DomainNotFoundException,
InvalidArgumentException {
ClassificationNotFoundException, ConcurrencyException, DomainNotFoundException,
InvalidArgumentException {
Classification classification =
classificationService.newClassification("TEST32451", "DOMAIN_A", "TASK");
classification = classificationService.createClassification(classification);
@ -139,8 +139,8 @@ class ClassificationServiceImplIntAutoCommitTest {
@Test
void testDefaultSettings()
throws NotAuthorizedException, ClassificationAlreadyExistException,
ClassificationNotFoundException, ConcurrencyException, DomainNotFoundException,
InvalidArgumentException {
ClassificationNotFoundException, ConcurrencyException, DomainNotFoundException,
InvalidArgumentException {
Classification classification =
classificationService.newClassification("TEST7771", "DOMAIN_A", "TASK");
classification = classificationService.createClassification(classification);

View File

@ -57,7 +57,6 @@ public class ClassificationServiceImplIntExplicitTest {
public static void resetDb() throws SQLException {
DataSource ds = TaskanaEngineTestConfiguration.getDataSource();
String schemaName = TaskanaEngineTestConfiguration.getSchemaName();
new SampleDataGenerator(ds, schemaName).dropDb();
}
@BeforeEach

View File

@ -1,5 +1,6 @@
-- the order is important!
DELETE FROM TASK_COMMENT;
DELETE FROM CONFIGURATION;
DELETE FROM ATTACHMENT;
DELETE FROM TASK;
DELETE FROM WORKBASKET_ACCESS_LIST;

View File

@ -1,5 +1,6 @@
DROP TABLE TASKANA_SCHEMA_VERSION;
DROP TABLE TASK_COMMENT;
DROP TABLE CONFIGURATION;
DROP TABLE ATTACHMENT;
DROP TABLE TASK;
DROP TABLE WORKBASKET_ACCESS_LIST;

View File

@ -2,7 +2,7 @@
spring.jpa.show-sql=true
spring.datasource.platform=h2
spring.datasource.initialize=true
spring.datasource.schema=classpath:sql/taskana-schema.sql
spring.datasource.schema=classpath:sql/taskana-schema-h2.sql
spring.h2.console.enabled=true
security.basic.enabled=false