This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 329770781 FINERACT-1831 - Encrypted tenant passwords - [x] Encrypt
tenant passwords - [x] Update integration tests - [x] Check application
database use encrypted passwords - [x] One time migration for the existing
password
329770781 is described below
commit 32977078178c66fcc3675780c5e77a3fbd4cbeae
Author: Janos Haber <[email protected]>
AuthorDate: Mon Feb 27 12:20:57 2023 +0100
FINERACT-1831 - Encrypted tenant passwords
- [x] Encrypt tenant passwords
- [x] Update integration tests
- [x] Check application database use encrypted passwords
- [x] One time migration for the existing password
---
.../core/config/FineractProperties.java | 4 +
.../domain/FineractPlatformTenantConnection.java | 116 ++-------------------
.../DataSourcePerTenantServiceFactory.java | 44 ++++----
.../database/DatabasePasswordEncryptor.java | 73 +++++++++++++
.../service/migration/TenantDataSourceFactory.java | 12 ++-
.../migration/TenantDatabaseUpgradeService.java | 4 +
.../migration/TenantPasswordEncryptionTask.java | 94 +++++++++++++++++
.../core/service/tenant/TenantMapper.java | 7 +-
.../security/service/PasswordEncryptor.java | 26 +++++
.../security/utils/EncryptionUtil.java | 97 +++++++++++++++++
.../src/main/resources/application.properties | 2 +
.../tenant-store/changelog-tenant-store.xml | 1 +
.../0007_encrypt_existing_tenant_passwords.xml} | 13 ++-
.../org/apache/fineract/TestConfiguration.java | 12 ++-
.../DataSourcePerTenantServiceFactoryTest.java | 29 +++++-
.../core/LiquibaseStepDefinitions.java | 9 +-
.../src/test/resources/application-test.properties | 2 +
17 files changed, 394 insertions(+), 151 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
index e84249351..c9c88fb61 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
@@ -67,6 +67,9 @@ public class FineractProperties {
private String identifier;
private String name;
private String description;
+ private String masterPassword;
+ private String encryption;
+
}
@Getter
@@ -244,4 +247,5 @@ public class FineractProperties {
private boolean regexWhitelistEnabled;
private List<String> regexWhitelist;
}
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java
index 6f2dee679..02181a759 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenantConnection.java
@@ -21,11 +21,13 @@ package org.apache.fineract.infrastructure.core.domain;
import java.io.Serializable;
import java.sql.Connection;
import javax.sql.DataSource;
+import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
/**
* Holds Tenant's DB server connection connection details.
*/
+@Getter
public class FineractPlatformTenantConnection implements Serializable {
private final Long connectionId;
@@ -56,6 +58,8 @@ public class FineractPlatformTenantConnection implements
Serializable {
private final int minEvictableIdleTimeMillis;
private final boolean testOnBorrow;
+ private final String masterPasswordHash;
+
public FineractPlatformTenantConnection(final Long connectionId, final
String schemaName, String schemaServer,
final String schemaServerPort, final String
schemaConnectionParameters, final String schemaUsername,
final String schemaPassword, final boolean autoUpdateEnabled,
final int initialSize, final long validationInterval,
@@ -63,7 +67,8 @@ public class FineractPlatformTenantConnection implements
Serializable {
final int abandonWhenPercentageFull, final int maxActive, final
int minIdle, final int maxIdle, final int suspectTimeout,
final int timeBetweenEvictionRunsMillis, final int
minEvictableIdleTimeMillis, final boolean tesOnBorrow,
final String readOnlySchemaServer, final String
readOnlySchemaServerPort, final String readOnlySchemaName,
- final String readOnlySchemaUsername, final String
readOnlySchemaPassword, final String readOnlySchemaConnectionParameters) {
+ final String readOnlySchemaUsername, final String
readOnlySchemaPassword, final String readOnlySchemaConnectionParameters,
+ final String masterPasswordHash) {
this.connectionId = connectionId;
this.schemaName = schemaName;
@@ -92,114 +97,7 @@ public class FineractPlatformTenantConnection implements
Serializable {
this.readOnlySchemaUsername = readOnlySchemaUsername;
this.readOnlySchemaPassword = readOnlySchemaPassword;
this.readOnlySchemaConnectionParameters =
readOnlySchemaConnectionParameters;
- }
-
- public String getSchemaServer() {
- return this.schemaServer;
- }
-
- public String getSchemaServerPort() {
- return this.schemaServerPort;
- }
-
- public String getSchemaConnectionParameters() {
- return this.schemaConnectionParameters;
- }
-
- public String getSchemaUsername() {
- return this.schemaUsername;
- }
-
- public String getSchemaPassword() {
- return this.schemaPassword;
- }
-
- public boolean isAutoUpdateEnabled() {
- return this.autoUpdateEnabled;
- }
-
- public int getInitialSize() {
- return this.initialSize;
- }
-
- public long getValidationInterval() {
- return this.validationInterval;
- }
-
- public boolean isRemoveAbandoned() {
- return this.removeAbandoned;
- }
-
- public int getRemoveAbandonedTimeout() {
- return this.removeAbandonedTimeout;
- }
-
- public boolean isLogAbandoned() {
- return this.logAbandoned;
- }
-
- public int getAbandonWhenPercentageFull() {
- return this.abandonWhenPercentageFull;
- }
-
- public int getMaxActive() {
- return this.maxActive;
- }
-
- public int getMinIdle() {
- return this.minIdle;
- }
-
- public int getMaxIdle() {
- return this.maxIdle;
- }
-
- public int getSuspectTimeout() {
- return this.suspectTimeout;
- }
-
- public int getTimeBetweenEvictionRunsMillis() {
- return this.timeBetweenEvictionRunsMillis;
- }
-
- public int getMinEvictableIdleTimeMillis() {
- return this.minEvictableIdleTimeMillis;
- }
-
- public boolean isTestOnBorrow() {
- return testOnBorrow;
- }
-
- public Long getConnectionId() {
- return connectionId;
- }
-
- public String getSchemaName() {
- return schemaName;
- }
-
- public String getReadOnlySchemaServer() {
- return readOnlySchemaServer;
- }
-
- public String getReadOnlySchemaServerPort() {
- return readOnlySchemaServerPort;
- }
-
- public String getReadOnlySchemaName() {
- return readOnlySchemaName;
- }
-
- public String getReadOnlySchemaUsername() {
- return readOnlySchemaUsername;
- }
-
- public String getReadOnlySchemaPassword() {
- return readOnlySchemaPassword;
- }
-
- public String getReadOnlySchemaConnectionParameters() {
- return readOnlySchemaConnectionParameters;
+ this.masterPasswordHash = masterPasswordHash;
}
@Override
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DataSourcePerTenantServiceFactory.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DataSourcePerTenantServiceFactory.java
index 7ce120335..e3e6bfc1b 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DataSourcePerTenantServiceFactory.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DataSourcePerTenantServiceFactory.java
@@ -24,9 +24,9 @@ import static
org.apache.fineract.infrastructure.core.domain.FineractPlatformTen
import com.zaxxer.hikari.HikariConfig;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import
org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
-import org.apache.fineract.infrastructure.security.constants.TenantConstants;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@@ -47,16 +47,24 @@ public class DataSourcePerTenantServiceFactory {
private final DataSource tenantDataSource;
private final HikariDataSourceFactory hikariDataSourceFactory;
+ private final DatabasePasswordEncryptor databasePasswordEncryptor;
+
public
DataSourcePerTenantServiceFactory(@Qualifier("hikariTenantDataSource")
DataSource tenantDataSource, HikariConfig hikariConfig,
- FineractProperties fineractProperties, ApplicationContext context,
HikariDataSourceFactory hikariDataSourceFactory) {
+ FineractProperties fineractProperties, ApplicationContext context,
HikariDataSourceFactory hikariDataSourceFactory,
+ DatabasePasswordEncryptor databasePasswordEncryptor) {
this.hikariConfig = hikariConfig;
this.fineractProperties = fineractProperties;
this.context = context;
this.tenantDataSource = tenantDataSource;
this.hikariDataSourceFactory = hikariDataSourceFactory;
+ this.databasePasswordEncryptor = databasePasswordEncryptor;
}
public DataSource createNewDataSourceFor(final
FineractPlatformTenantConnection tenantConnection) {
+ if
(!databasePasswordEncryptor.isMasterPasswordHashValid(tenantConnection.getMasterPasswordHash()))
{
+ throw new IllegalArgumentException(
+ "Invalid master password on tenant connection
%d.".formatted(tenantConnection.getConnectionId()));
+ }
String protocol = toProtocol(tenantDataSource);
// Default properties for Writing
String schemaServer = tenantConnection.getSchemaServer();
@@ -67,18 +75,13 @@ public class DataSourcePerTenantServiceFactory {
String schemaConnectionParameters =
tenantConnection.getSchemaConnectionParameters();
// Properties to ReadOnly case
if (fineractProperties.getMode().isReadOnlyMode()) {
- schemaServer =
getPropertyValue(tenantConnection.getReadOnlySchemaServer(),
TenantConstants.PROPERTY_RO_SCHEMA_SERVER_NAME,
- schemaServer);
- schemaPort =
getPropertyValue(tenantConnection.getReadOnlySchemaServerPort(),
TenantConstants.PROPERTY_RO_SCHEMA_SERVER_PORT,
- schemaPort);
- schemaName =
getPropertyValue(tenantConnection.getReadOnlySchemaName(),
TenantConstants.PROPERTY_RO_SCHEMA_SCHEMA_NAME,
- schemaName);
- schemaUsername =
getPropertyValue(tenantConnection.getReadOnlySchemaUsername(),
TenantConstants.PROPERTY_RO_SCHEMA_USERNAME,
- schemaUsername);
- schemaPassword =
getPropertyValue(tenantConnection.getReadOnlySchemaPassword(),
TenantConstants.PROPERTY_RO_SCHEMA_PASSWORD,
- schemaPassword);
- schemaConnectionParameters =
getPropertyValue(tenantConnection.getReadOnlySchemaConnectionParameters(),
- TenantConstants.PROPERTY_RO_SCHEMA_CONNECTION_PARAMETERS,
schemaConnectionParameters);
+ schemaServer =
StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaServer(),
schemaServer);
+ schemaPort =
StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaServerPort(),
schemaPort);
+ schemaName =
StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaName(),
schemaName);
+ schemaUsername =
StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaUsername(),
schemaUsername);
+ schemaPassword =
StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaPassword(),
schemaPassword);
+ schemaConnectionParameters =
StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaConnectionParameters(),
+ schemaConnectionParameters);
}
String jdbcUrl = toJdbcUrl(protocol, schemaServer, schemaPort,
schemaName, schemaConnectionParameters);
log.debug("{}", jdbcUrl);
@@ -88,7 +91,7 @@ public class DataSourcePerTenantServiceFactory {
config.setJdbcUrl(jdbcUrl);
config.setPoolName(schemaName + "_pool");
config.setUsername(schemaUsername);
- config.setPassword(schemaPassword);
+ config.setPassword(databasePasswordEncryptor.decrypt(schemaPassword));
config.setMinimumIdle(tenantConnection.getInitialSize());
config.setMaximumPoolSize(tenantConnection.getMaxActive());
config.setValidationTimeout(tenantConnection.getValidationInterval());
@@ -108,15 +111,4 @@ public class DataSourcePerTenantServiceFactory {
return hikariDataSourceFactory.create(config);
}
- private String getPropertyValue(final String baseValue, final String
propertyName, final String defaultValue) {
- // If the property already has set, return It
- if (null != baseValue) {
- return baseValue;
- }
- if (context == null) {
- return defaultValue;
- }
- return context.getEnvironment().getProperty(propertyName,
defaultValue);
- }
-
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
new file mode 100644
index 000000000..033a13669
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.service.database;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import org.apache.fineract.infrastructure.security.service.PasswordEncryptor;
+import org.apache.fineract.infrastructure.security.utils.EncryptionUtil;
+import org.springframework.security.crypto.bcrypt.BCrypt;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class DatabasePasswordEncryptor implements PasswordEncryptor {
+
+ public static final String DEFAULT_ENCRYPTION = "AES/CBC/PKCS5Padding";
+
+ public static final String DEFAULT_MASTER_PASSWORD = "fineract";
+
+ private final FineractProperties fineractProperties;
+
+ @Override
+ public String encrypt(String plainPassword) {
+ String masterPassword =
Optional.ofNullable(fineractProperties.getTenant())
+
.map(FineractProperties.FineractTenantProperties::getMasterPassword).orElse(DEFAULT_MASTER_PASSWORD);
+ String encryption = Optional.ofNullable(fineractProperties.getTenant())
+
.map(FineractProperties.FineractTenantProperties::getEncryption).orElse(DEFAULT_ENCRYPTION);
+ return EncryptionUtil.encryptToBase64(encryption, masterPassword,
plainPassword);
+ }
+
+ @Override
+ public String decrypt(String encryptedPassword) {
+ String masterPassword =
Optional.ofNullable(fineractProperties.getTenant())
+
.map(FineractProperties.FineractTenantProperties::getMasterPassword).orElse(DEFAULT_MASTER_PASSWORD);
+ String encryption = Optional.ofNullable(fineractProperties.getTenant())
+
.map(FineractProperties.FineractTenantProperties::getEncryption).orElse(DEFAULT_ENCRYPTION);
+ return EncryptionUtil.decryptFromBase64(encryption, masterPassword,
encryptedPassword);
+ }
+
+ public String getMasterPasswordHash() {
+ String masterPassword = Optional.ofNullable(fineractProperties) //
+ .map(FineractProperties::getTenant) //
+
.map(FineractProperties.FineractTenantProperties::getMasterPassword) //
+ .orElse(DEFAULT_MASTER_PASSWORD);
+ return BCrypt.hashpw(masterPassword.getBytes(StandardCharsets.UTF_8),
BCrypt.gensalt());
+ }
+
+ public boolean isMasterPasswordHashValid(String hashed) {
+ String masterPassword = Optional.ofNullable(fineractProperties) //
+ .map(FineractProperties::getTenant) //
+
.map(FineractProperties.FineractTenantProperties::getMasterPassword) //
+ .orElse(DEFAULT_MASTER_PASSWORD);
+ return BCrypt.checkpw(masterPassword, hashed);
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java
index 11c3f6aec..17e66ed02 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java
@@ -25,6 +25,7 @@ import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import
org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
+import
org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,9 +39,13 @@ public class TenantDataSourceFactory {
private final HikariDataSource tenantDataSource;
+ private final DatabasePasswordEncryptor databasePasswordEncryptor;
+
@Autowired
- public TenantDataSourceFactory(@Qualifier("hikariTenantDataSource")
HikariDataSource tenantDataSource) {
+ public TenantDataSourceFactory(@Qualifier("hikariTenantDataSource")
HikariDataSource tenantDataSource,
+ DatabasePasswordEncryptor databasePasswordEncryptor) {
this.tenantDataSource = tenantDataSource;
+ this.databasePasswordEncryptor = databasePasswordEncryptor;
}
public DataSource create(FineractPlatformTenant tenant) {
@@ -53,8 +58,11 @@ public class TenantDataSourceFactory {
dataSource.setConnectionTestQuery(tenantDataSource.getConnectionTestQuery());
FineractPlatformTenantConnection tenantConnection =
tenant.getConnection();
+ if
(!databasePasswordEncryptor.isMasterPasswordHashValid(tenantConnection.getMasterPasswordHash()))
{
+ throw new IllegalArgumentException("Invalid master password");
+ }
dataSource.setUsername(tenantConnection.getSchemaUsername());
- dataSource.setPassword(tenantConnection.getSchemaPassword());
+
dataSource.setPassword(databasePasswordEncryptor.decrypt(tenantConnection.getSchemaPassword()));
String protocol = toProtocol(tenantDataSource);
String tenantJdbcUrl = toJdbcUrl(protocol,
tenantConnection.getSchemaServer(), tenantConnection.getSchemaServerPort(),
tenantConnection.getSchemaName(),
tenantConnection.getSchemaConnectionParameters());
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
index aee562ba2..623c49532 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
@@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import javax.sql.DataSource;
+import liquibase.change.custom.CustomTaskChange;
import liquibase.exception.LiquibaseException;
import liquibase.integration.spring.SpringLiquibase;
import lombok.RequiredArgsConstructor;
@@ -59,6 +60,9 @@ public class TenantDatabaseUpgradeService implements
InitializingBean {
private final TenantDataSourceFactory tenantDataSourceFactory;
private final Environment environment;
+ // DO NOT REMOVE! Required for liquibase custom task initialization
+ private final List<CustomTaskChange>
customTaskChangesForDependencyInjection;
+
@Override
public void afterPropertiesSet() throws Exception {
if (notLiquibaseOnlyMode()) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java
new file mode 100644
index 000000000..e8d693fd8
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantPasswordEncryptionTask.java
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.service.migration;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import liquibase.change.custom.CustomTaskChange;
+import liquibase.database.Database;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.CustomChangeException;
+import liquibase.exception.SetupException;
+import liquibase.exception.ValidationErrors;
+import liquibase.resource.ResourceAccessor;
+import
org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class TenantPasswordEncryptionTask implements CustomTaskChange,
ApplicationContextAware {
+
+ private static DatabasePasswordEncryptor databasePasswordEncryptor;
+
+ @Override
+ public void execute(Database database) throws CustomChangeException {
+ JdbcConnection dbConn = (JdbcConnection) database.getConnection(); //
autocommit is false
+ try (Statement selectStatement = dbConn.createStatement(); Statement
updateStatement = dbConn.createStatement()) {
+
+ try (ResultSet rs = selectStatement.executeQuery("SELECT id,
schema_password FROM tenant_server_connections")) {
+ while (rs.next()) {
+ String id = rs.getString("id");
+ String schemaPassword = rs.getString("schema_password");
+ String encryptedPassword =
TenantPasswordEncryptionTask.databasePasswordEncryptor.encrypt(schemaPassword);
+
+ String updateSql = String.format(
+ "update tenant_server_connections set
schema_password = '%s', master_password_hash = '%s' where id = %s",
+ encryptedPassword,
TenantPasswordEncryptionTask.databasePasswordEncryptor.getMasterPasswordHash(),
id);
+ updateStatement.execute(updateSql);
+ }
+ }
+
+ } catch (Exception e) {
+ throw new CustomChangeException(e);
+ }
+ }
+
+ @Override
+ public String getConfirmationMessage() {
+ return null;
+ }
+
+ @Override
+ public void setUp() throws SetupException {
+ // Not required
+ }
+
+ @Override
+ public void setFileOpener(ResourceAccessor resourceAccessor) {
+ // Not required
+ }
+
+ @Override
+ public ValidationErrors validate(Database database) {
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("static-access")
+ @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
+ public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
+ TenantPasswordEncryptionTask.databasePasswordEncryptor =
applicationContext.getBean(DatabasePasswordEncryptor.class);
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/TenantMapper.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/TenantMapper.java
index 979639d0b..bb17ca50a 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/TenantMapper.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/TenantMapper.java
@@ -40,8 +40,8 @@ public final class TenantMapper implements
RowMapper<FineractPlatformTenant> {
+ " ts.readonly_schema_server as readOnlySchemaServer, " + "
ts.readonly_schema_server_port as readOnlySchemaServerPort, "
+ " ts.readonly_schema_name as readOnlySchemaName, " + "
ts.readonly_schema_username as readOnlySchemaUsername, "
+ " ts.readonly_schema_password as readOnlySchemaPassword, "
- + " ts.readonly_schema_connection_parameters as
readOnlySchemaConnectionParameters "
- + " from tenants t left join tenant_server_connections ts ";
+ + " ts.readonly_schema_connection_parameters as
readOnlySchemaConnectionParameters, "
+ + " ts.master_password_hash as masterPasswordHash " + " from
tenants t left join tenant_server_connections ts ";
private final StringBuilder sqlBuilder = new
StringBuilder(TENANT_SERVER_CONNECTION_BUILDER);
public TenantMapper(boolean isReport) {
@@ -98,11 +98,12 @@ public final class TenantMapper implements
RowMapper<FineractPlatformTenant> {
final int suspectTimeout = rs.getInt("poolSuspectTimeout");
final int timeBetweenEvictionRunsMillis =
rs.getInt("poolTimeBetweenEvictionRunsMillis");
final int minEvictableIdleTimeMillis =
rs.getInt("poolMinEvictableIdleTimeMillis");
+ final String masterPasswordHash = rs.getString("masterPasswordHash");
return new FineractPlatformTenantConnection(connectionId, schemaName,
schemaServer, schemaServerPort, schemaConnectionParameters,
schemaUsername, schemaPassword, autoUpdateEnabled,
initialSize, validationInterval, removeAbandoned, removeAbandonedTimeout,
logAbandoned, abandonWhenPercentageFull, maxActive, minIdle,
maxIdle, suspectTimeout, timeBetweenEvictionRunsMillis,
minEvictableIdleTimeMillis, testOnBorrow,
readOnlySchemaServer, readOnlySchemaServerPort, readOnlySchemaName,
- readOnlySchemaUsername, readOnlySchemaPassword,
readOnlySchemaConnectionParameters);
+ readOnlySchemaUsername, readOnlySchemaPassword,
readOnlySchemaConnectionParameters, masterPasswordHash);
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PasswordEncryptor.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PasswordEncryptor.java
new file mode 100644
index 000000000..64ad4fc94
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PasswordEncryptor.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.service;
+
+public interface PasswordEncryptor {
+
+ String encrypt(String plainPassword);
+
+ String decrypt(String encryptedPassword);
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/EncryptionUtil.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/EncryptionUtil.java
new file mode 100644
index 000000000..003c60941
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/EncryptionUtil.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.utils;
+
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Base64;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.validation.constraints.NotNull;
+
+public final class EncryptionUtil {
+
+ private static SecureRandom random = new SecureRandom();
+
+ private EncryptionUtil() {}
+
+ public static String encryptToBase64(@NotNull String cipherType, @NotNull
String masterPassword, @NotNull String data) {
+ try {
+ Cipher cipher = Cipher.getInstance(cipherType);
+ byte[] salt = new byte[16];
+ byte[] ivBytes = new byte[16];
+ random.nextBytes(salt);
+ random.nextBytes(ivBytes);
+
+ IvParameterSpec iv = new IvParameterSpec(ivBytes);
+
+ SecretKeySpec keySpec = generateKeySpec(masterPassword, salt);
+
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
+ byte[] encValue =
cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
+ byte[] finalCiphertext = new byte[encValue.length + 2 * 16];
+
+ System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
+ System.arraycopy(salt, 0, finalCiphertext, 16, 16);
+ System.arraycopy(encValue, 0, finalCiphertext, 32,
encValue.length);
+ return Base64.getEncoder().encodeToString(finalCiphertext);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unable to encrypt data. Please
check if the master password and algorithm is correct.", e);
+ }
+ }
+
+ public static String decryptFromBase64(@NotNull String cipherType,
@NotNull String masterPassword, @NotNull String encryptedData) {
+ try {
+ Cipher cipher = Cipher.getInstance(cipherType);
+ byte[] rawData = Base64.getDecoder().decode(encryptedData);
+ byte[] ivBytes = new byte[16];
+ byte[] salt = new byte[16];
+ byte[] ciphertext = new byte[rawData.length - 32];
+
+ System.arraycopy(rawData, 0, ivBytes, 0, 16);
+ System.arraycopy(rawData, 16, salt, 0, 16);
+ System.arraycopy(rawData, 32, ciphertext, 0, rawData.length - 32);
+
+ SecretKeySpec keySpec = generateKeySpec(masterPassword, salt);
+
+ IvParameterSpec iv = new IvParameterSpec(ivBytes);
+
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
+ return new String(cipher.doFinal(ciphertext),
StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unable to decrypt data. Please
check if the master password and algorithm is correct.", e);
+ }
+ }
+
+ @NotNull
+ private static SecretKeySpec generateKeySpec(String masterPassword, byte[]
salt)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt,
65536, 256); // AES-256
+ SecretKeyFactory f =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ byte[] key = f.generateSecret(spec).getEncoded();
+ return new SecretKeySpec(key, "AES");
+ }
+
+}
diff --git a/fineract-provider/src/main/resources/application.properties
b/fineract-provider/src/main/resources/application.properties
index 7c37d32b5..11677c9df 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -34,6 +34,8 @@
fineract.tenant.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata}
fineract.tenant.identifier=${FINERACT_DEFAULT_TENANTDB_IDENTIFIER:default}
fineract.tenant.name=${FINERACT_DEFAULT_TENANTDB_NAME:fineract_default}
fineract.tenant.description=${FINERACT_DEFAULT_TENANTDB_DESCRIPTION:Default
Demo Tenant}
+fineract.tenant.master-password=${FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD:fineract}
+fineract.tenant.encrytion=${FINERACT_DEFAULT_TENANTDB_ENCRYPTION:"AES/CBC/PKCS5Padding"}
fineract.mode.read-enabled=${FINERACT_MODE_READ_ENABLED:true}
fineract.mode.write-enabled=${FINERACT_MODE_WRITE_ENABLED:true}
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
index 402892a9a..3cc73b305 100644
---
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
@@ -26,4 +26,5 @@
<include file="parts/0004_readonly_database_connection.xml"
relativeToChangelogFile="true"/>
<include file="parts/0005_jdbc_connection_string.xml"
relativeToChangelogFile="true"/>
<include file="parts/0006_drop_retry_parameter_columns.xml"
relativeToChangelogFile="true"/>
+ <include file="parts/0007_encrypt_existing_tenant_passwords.xml"
relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0007_encrypt_existing_tenant_passwords.xml
similarity index 67%
copy from
fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
copy to
fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0007_encrypt_existing_tenant_passwords.xml
index 402892a9a..b6136db31 100644
---
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0007_encrypt_existing_tenant_passwords.xml
@@ -22,8 +22,13 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
- <include file="parts/0003_reset_postgresql_sequences.xml"
relativeToChangelogFile="true"/>
- <include file="parts/0004_readonly_database_connection.xml"
relativeToChangelogFile="true"/>
- <include file="parts/0005_jdbc_connection_string.xml"
relativeToChangelogFile="true"/>
- <include file="parts/0006_drop_retry_parameter_columns.xml"
relativeToChangelogFile="true"/>
+ <changeSet author="fineract" id="1" context="tenant_store_db">
+ <modifyDataType tableName="tenant_server_connections"
columnName="schema_password" newDataType="varchar(255)"/>
+ <addColumn tableName="tenant_server_connections">
+ <column name="master_password_hash" type="varchar(255)"></column>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="2" context="tenant_store_db">
+ <customChange
class="org.apache.fineract.infrastructure.core.service.migration.TenantPasswordEncryptionTask"/>
+ </changeSet>
</databaseChangeLog>
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
index 98a6ddad1..67a8e4f91 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
@@ -22,10 +22,13 @@ import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.mock;
import com.zaxxer.hikari.HikariDataSource;
+import java.util.List;
import javax.sql.DataSource;
+import liquibase.change.custom.CustomTaskChange;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import
org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService;
+import
org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import
org.apache.fineract.infrastructure.core.service.migration.ExtendedSpringLiquibaseFactory;
import
org.apache.fineract.infrastructure.core.service.migration.TenantDataSourceFactory;
import
org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseStateVerifier;
@@ -83,8 +86,8 @@ import
org.springframework.transaction.annotation.EnableTransactionManagement;
public class TestConfiguration {
@Bean
- public TenantDataSourceFactory tenantDataSourceFactory() {
- return new TenantDataSourceFactory(null) {
+ public TenantDataSourceFactory
tenantDataSourceFactory(DatabasePasswordEncryptor databasePasswordEncryptor) {
+ return new TenantDataSourceFactory(null, databasePasswordEncryptor) {
@Override
public DataSource create(FineractPlatformTenant tenant) {
@@ -151,9 +154,10 @@ public class TestConfiguration {
public TenantDatabaseUpgradeService
tenantDatabaseUpgradeService(TenantDetailsService tenantDetailsService,
HikariDataSource tenantDataSource, TenantDatabaseStateVerifier
tenantDatabaseStateVerifier,
ExtendedSpringLiquibaseFactory liquibaseFactory,
TenantDataSourceFactory tenantDataSourceFactory,
- FineractProperties fineractProperties, Environment environment) {
+ FineractProperties fineractProperties, Environment environment,
+ List<CustomTaskChange> customTaskChangesForDependencyInjection) {
return new TenantDatabaseUpgradeService(tenantDetailsService,
tenantDataSource, fineractProperties, tenantDatabaseStateVerifier,
- liquibaseFactory, tenantDataSourceFactory, environment);
+ liquibaseFactory, tenantDataSourceFactory, environment,
customTaskChangesForDependencyInjection);
}
/**
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/DataSourcePerTenantServiceFactoryTest.java
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/DataSourcePerTenantServiceFactoryTest.java
index 20d8df67c..b099cd397 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/DataSourcePerTenantServiceFactoryTest.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/DataSourcePerTenantServiceFactoryTest.java
@@ -38,7 +38,9 @@ import
org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import
org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
import
org.apache.fineract.infrastructure.core.service.database.DataSourcePerTenantServiceFactory;
+import
org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import
org.apache.fineract.infrastructure.core.service.database.HikariDataSourceFactory;
+import org.apache.fineract.infrastructure.security.utils.EncryptionUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -49,6 +51,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
+import org.springframework.security.crypto.bcrypt.BCrypt;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -81,6 +84,10 @@ public class DataSourcePerTenantServiceFactoryTest {
public static final String MASTER_DB_CONN_TEST_QUERY = "SELECT 1";
public static final boolean MASTER_DB_AUTO_COMMIT_ENABLED = true;
+ public static final String MASTER_MASTER_PASSWORD = "fineract";
+
+ public static final String MASTER_ENCRYPTION = "AES/CBC/PKCS5Padding";
+
@Mock
private FineractProperties fineractProperties;
@@ -102,6 +109,9 @@ public class DataSourcePerTenantServiceFactoryTest {
@Captor
private ArgumentCaptor<HikariConfig> hikariConfigCaptor;
+ @Mock
+ private DatabasePasswordEncryptor databasePasswordEncryptor;
+
@InjectMocks
private DataSourcePerTenantServiceFactory underTest;
@@ -119,15 +129,19 @@ public class DataSourcePerTenantServiceFactoryTest {
given(tenantConnection.getSchemaServerPort()).willReturn(MASTER_DB_SERVER_PORT);
given(tenantConnection.getSchemaName()).willReturn(MASTER_DB_SCHEMA_NAME);
given(tenantConnection.getSchemaUsername()).willReturn(MASTER_DB_USERNAME);
-
given(tenantConnection.getSchemaPassword()).willReturn(MASTER_DB_PASSWORD);
+ given(tenantConnection.getSchemaPassword())
+ .willReturn(EncryptionUtil.encryptToBase64(MASTER_ENCRYPTION,
MASTER_MASTER_PASSWORD, MASTER_DB_PASSWORD));
given(tenantConnection.getSchemaConnectionParameters()).willReturn(MASTER_DB_CONN_PARAMS);
given(tenantConnection.getReadOnlySchemaServer()).willReturn(READONLY_DB_SERVER);
given(tenantConnection.getReadOnlySchemaServerPort()).willReturn(READONLY_DB_SERVER_PORT);
given(tenantConnection.getReadOnlySchemaName()).willReturn(READONLY_DB_SCHEMA_NAME);
given(tenantConnection.getReadOnlySchemaUsername()).willReturn(READONLY_DB_USERNAME);
-
given(tenantConnection.getReadOnlySchemaPassword()).willReturn(READONLY_DB_PASSWORD);
+ given(tenantConnection.getReadOnlySchemaPassword())
+ .willReturn(EncryptionUtil.encryptToBase64(MASTER_ENCRYPTION,
MASTER_MASTER_PASSWORD, READONLY_DB_PASSWORD));
given(tenantConnection.getReadOnlySchemaConnectionParameters()).willReturn(READONLY_DB_CONN_PARAMS);
+ String hashedMasterPassword = BCrypt.hashpw("master-password",
BCrypt.gensalt());
+
given(tenantConnection.getMasterPasswordHash()).willReturn(hashedMasterPassword);
given(defaultTenant.getConnection()).willReturn(tenantConnection);
given(tenantConnection.getInitialSize()).willReturn(MASTER_DB_INITIAL_SIZE);
@@ -141,6 +155,17 @@ public class DataSourcePerTenantServiceFactoryTest {
given(tenantHikariConfig.isAutoCommit()).willReturn(MASTER_DB_AUTO_COMMIT_ENABLED);
given(hikariDataSourceFactory.create(any())).willReturn(mock(HikariDataSource.class));
+ FineractProperties.FineractTenantProperties tenantPropertiesMock =
mock(FineractProperties.FineractTenantProperties.class);
+
given(tenantPropertiesMock.getEncryption()).willReturn(MASTER_ENCRYPTION);
+
given(tenantPropertiesMock.getMasterPassword()).willReturn(MASTER_MASTER_PASSWORD);
+ given(fineractProperties.getTenant()).willReturn(tenantPropertiesMock);
+
+
given(databasePasswordEncryptor.isMasterPasswordHashValid(any())).willReturn(true);
+
given(databasePasswordEncryptor.getMasterPasswordHash()).willReturn(hashedMasterPassword);
+ given(databasePasswordEncryptor.decrypt(any())).will(
+ answer -> EncryptionUtil.decryptFromBase64(MASTER_ENCRYPTION,
MASTER_MASTER_PASSWORD, answer.getArgument(0, String.class)));
+ given(databasePasswordEncryptor.encrypt(any())).will(
+ answer -> EncryptionUtil.encryptToBase64(MASTER_ENCRYPTION,
MASTER_MASTER_PASSWORD, answer.getArgument(0, String.class)));
}
@Test
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
index 27b64e415..8608a964c 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
@@ -29,16 +29,19 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.cucumber.java8.En;
+import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import
org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import
org.apache.fineract.infrastructure.core.service.migration.ExtendedSpringLiquibase;
import
org.apache.fineract.infrastructure.core.service.migration.ExtendedSpringLiquibaseFactory;
import
org.apache.fineract.infrastructure.core.service.migration.SchemaUpgradeNeededException;
import
org.apache.fineract.infrastructure.core.service.migration.TenantDataSourceFactory;
import
org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseStateVerifier;
import
org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService;
+import
org.apache.fineract.infrastructure.core.service.migration.TenantPasswordEncryptionTask;
import
org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
@@ -65,6 +68,8 @@ public class LiquibaseStepDefinitions implements En {
private DataSource defaultTenantDataSource;
private Environment environment;
+ private DatabasePasswordEncryptor databasePasswordEncryptor;
+
public LiquibaseStepDefinitions() {
Given("Liquibase is disabled with a default tenant", () -> {
initializeLiquibase(false);
@@ -169,6 +174,8 @@ public class LiquibaseStepDefinitions implements En {
defaultTenantDataSource = mock(DataSource.class);
+ TenantPasswordEncryptionTask tenantPasswordEncryptor =
mock(TenantPasswordEncryptionTask.class);
+
given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(!liquibaseEnabled);
given(liquibaseFactory.create(tenantStoreDataSource,
"tenant_store_db", "initial_switch")).willReturn(initialTenantStoreLiquibase);
given(liquibaseFactory.create(tenantStoreDataSource,
"tenant_store_db")).willReturn(tenantStoreLiquibase);
@@ -180,6 +187,6 @@ public class LiquibaseStepDefinitions implements En {
given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db",
"custom_changelog")).willReturn(customChangeLogLiquibase);
tenantDatabaseUpgradeService = new
TenantDatabaseUpgradeService(tenantDetailsService, tenantStoreDataSource,
fineractProperties,
- databaseStateVerifier, liquibaseFactory,
tenantDataSourceFactory, environment);
+ databaseStateVerifier, liquibaseFactory,
tenantDataSourceFactory, environment, Arrays.asList(tenantPasswordEncryptor));
}
}
diff --git a/fineract-provider/src/test/resources/application-test.properties
b/fineract-provider/src/test/resources/application-test.properties
index 3e2f22432..f33d80bea 100644
--- a/fineract-provider/src/test/resources/application-test.properties
+++ b/fineract-provider/src/test/resources/application-test.properties
@@ -32,6 +32,8 @@ fineract.tenant.timezone=Asia/Kolkata
fineract.tenant.identifier=default
fineract.tenant.name=fineract_default
fineract.tenant.description=Default Demo Tenant
+fineract.tenant.master-password=fineract
+fineract.tenant.encrytion="AES/CBC/PKCS5Padding"
fineract.mode.read-enabled=true
fineract.mode.write-enabled=true