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
This commit is contained in:
committed by
Arnold Gálovics
parent
f1c89ac914
commit
3297707817
+4
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-109
@@ -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
|
||||
|
||||
+18
-26
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+73
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -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());
|
||||
|
||||
+4
@@ -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()) {
|
||||
|
||||
+94
@@ -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);
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -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);
|
||||
}
|
||||
+97
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<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">
|
||||
<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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+27
-2
@@ -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
|
||||
|
||||
+8
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user