TSK-1218: Prevent unsecure access to TASKANA
This commit is contained in:
parent
56910acda0
commit
35e7db4355
|
@ -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<>()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue