FINERACT-2461: Replace SQL string concatenation with parameterized queries
Fix ~30 SQL injection vulnerabilities across datatable services by replacing string concatenation with JDBC parameterized queries (?). Files changed: - DatatableWriteServiceImpl: parameterized DELETE, INSERT, SELECT queries - DatatableUtil: parameterized dataScopedSQL, office join conditions - GenericDataService/Impl: added parameterized fillResultsetRowData overload - DatatableReadServiceImpl: parameterized countDatatableEntries - WriteSurveyServiceImpl: removed duplicate vulnerable getPermissionSql - DepositAccountReadPlatformServiceImpl: added pagination validation Tests added: - DatatableUtilTest: 11 tests for parameterized dataScopedSQL and joins - DatatableWriteServiceImplTest: 7 tests for parameterized CRUD queries - DatatableReadServiceImplCountTest: 1 test for parameterized count query Do good with AI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Adam Saghy
co-authored by
Claude Opus 4.6
parent
87e328e9e7
commit
054d18b020
+2
-2
@@ -263,8 +263,8 @@ public class DatatableReadServiceImpl implements DatatableReadService {
|
||||
@Override
|
||||
public Long countDatatableEntries(final String datatableName, final Long appTableId, String foreignKeyColumn) {
|
||||
final String sqlString = "SELECT COUNT(" + sqlGenerator.escape(foreignKeyColumn) + ") FROM " + sqlGenerator.escape(datatableName)
|
||||
+ " WHERE " + sqlGenerator.escape(foreignKeyColumn) + " = " + appTableId;
|
||||
return this.jdbcTemplate.queryForObject(sqlString, Long.class); // NOSONAR
|
||||
+ " WHERE " + sqlGenerator.escape(foreignKeyColumn) + " = ?";
|
||||
return this.jdbcTemplate.queryForObject(sqlString, Long.class, appTableId); // NOSONAR
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+76
-45
@@ -23,6 +23,7 @@ import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiCon
|
||||
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_FIELD_ID;
|
||||
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_REGISTERED_TABLE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -112,9 +113,10 @@ public class DatatableUtil {
|
||||
}
|
||||
|
||||
public CommandProcessingResult checkMainResourceExistsWithinScope(@NonNull EntityTables entityTable, final Long appTableId) {
|
||||
final String sql = dataScopedSQL(entityTable, appTableId);
|
||||
final List<Object> params = new ArrayList<>();
|
||||
final String sql = dataScopedSQL(entityTable, appTableId, params);
|
||||
log.debug("data scoped sql: {}", sql);
|
||||
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
|
||||
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql, params.toArray());
|
||||
|
||||
if (!rs.next()) {
|
||||
throw new DatatableNotFoundException(entityTable, appTableId);
|
||||
@@ -140,60 +142,88 @@ public class DatatableUtil {
|
||||
.build();
|
||||
}
|
||||
|
||||
public String dataScopedSQL(@NonNull EntityTables entityTable, final Long appTableId) {
|
||||
public String dataScopedSQL(@NonNull EntityTables entityTable, final Long appTableId, final List<Object> params) {
|
||||
// unfortunately have to, one way or another, be able to restrict data to the users office hierarchy. Here, a
|
||||
// few key tables are done. But if additional fields are needed on other tables the same pattern applies
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
String officeHierarchy = currentUser.getOffice().getHierarchy();
|
||||
String hierarchyPattern = officeHierarchy + "%";
|
||||
// m_loan and m_savings_account are connected to an m_office through either an m_client or an m_group If both it
|
||||
// means it relates to an m_client that is in a group (still an m_client account)
|
||||
return switch (entityTable) {
|
||||
case LOAN -> "select distinct x.* from ( "
|
||||
+ "(select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as transactionId, null as entityId from m_loan l "
|
||||
+ getClientOfficeJoinCondition(officeHierarchy, "l") + " where l.id = " + appTableId + ")" + " union all "
|
||||
+ "(select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as transactionId, null as entityId from m_loan l "
|
||||
+ getGroupOfficeJoinCondition(officeHierarchy, "l") + " where l.id = " + appTableId + ")" + " ) as x";
|
||||
case SAVINGS -> "select distinct x.* from ( "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as transactionId, null as entityId "
|
||||
+ "from m_savings_account s " + getClientOfficeJoinCondition(officeHierarchy, "s") + " where s.id = " + appTableId + ")"
|
||||
+ " union all "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as transactionId, null as entityId "
|
||||
+ "from m_savings_account s " + getGroupOfficeJoinCondition(officeHierarchy, "s") + " where s.id = " + appTableId + ")"
|
||||
+ " ) as x";
|
||||
case SAVINGS_TRANSACTION -> "select distinct x.* from ( "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, t.id as transactionId, null as entityId "
|
||||
+ "from m_savings_account_transaction t join m_savings_account s on t.savings_account_id = s.id "
|
||||
+ getClientOfficeJoinCondition(officeHierarchy, "s") + " where t.id = " + appTableId + ")" + " union all "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, t.id as transactionId, null as entityId "
|
||||
+ "from m_savings_account_transaction t join m_savings_account s on t.savings_account_id = s.id "
|
||||
+ getGroupOfficeJoinCondition(officeHierarchy, "s") + " where t.id = " + appTableId + ")" + " ) as x";
|
||||
case CLIENT ->
|
||||
"select o.id as officeId, null as groupId, c.id as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_client c "
|
||||
+ getOfficeJoinCondition(officeHierarchy, "c") + " where c.id = " + appTableId;
|
||||
case GROUP, CENTER ->
|
||||
"select o.id as officeId, g.id as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_group g "
|
||||
+ getOfficeJoinCondition(officeHierarchy, "g") + " where g.id = " + appTableId;
|
||||
case OFFICE ->
|
||||
"select o.id as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_office o "
|
||||
+ "where o.hierarchy like '" + officeHierarchy + "%'" + " and o.id = " + appTableId;
|
||||
case LOAN_PRODUCT, SAVINGS_PRODUCT, SHARE_PRODUCT ->
|
||||
"select null as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, p.id as entityId from "
|
||||
+ entityTable.getName() + " as p WHERE p.id = " + appTableId;
|
||||
case LOAN -> {
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
yield "select distinct x.* from ( "
|
||||
+ "(select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as transactionId, null as entityId from m_loan l "
|
||||
+ getClientOfficeJoinCondition("l") + " where l.id = ?)" + " union all "
|
||||
+ "(select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as transactionId, null as entityId from m_loan l "
|
||||
+ getGroupOfficeJoinCondition("l") + " where l.id = ?)" + " ) as x";
|
||||
}
|
||||
case SAVINGS -> {
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
yield "select distinct x.* from ( "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as transactionId, null as entityId "
|
||||
+ "from m_savings_account s " + getClientOfficeJoinCondition("s") + " where s.id = ?)" + " union all "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as transactionId, null as entityId "
|
||||
+ "from m_savings_account s " + getGroupOfficeJoinCondition("s") + " where s.id = ?)" + " ) as x";
|
||||
}
|
||||
case SAVINGS_TRANSACTION -> {
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
yield "select distinct x.* from ( "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, t.id as transactionId, null as entityId "
|
||||
+ "from m_savings_account_transaction t join m_savings_account s on t.savings_account_id = s.id "
|
||||
+ getClientOfficeJoinCondition("s") + " where t.id = ?)" + " union all "
|
||||
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, t.id as transactionId, null as entityId "
|
||||
+ "from m_savings_account_transaction t join m_savings_account s on t.savings_account_id = s.id "
|
||||
+ getGroupOfficeJoinCondition("s") + " where t.id = ?)" + " ) as x";
|
||||
}
|
||||
case CLIENT -> {
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
yield "select o.id as officeId, null as groupId, c.id as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_client c "
|
||||
+ getOfficeJoinCondition("c") + " where c.id = ?";
|
||||
}
|
||||
case GROUP, CENTER -> {
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
yield "select o.id as officeId, g.id as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_group g "
|
||||
+ getOfficeJoinCondition("g") + " where g.id = ?";
|
||||
}
|
||||
case OFFICE -> {
|
||||
params.add(hierarchyPattern);
|
||||
params.add(appTableId);
|
||||
yield "select o.id as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_office o "
|
||||
+ "where o.hierarchy like ? and o.id = ?";
|
||||
}
|
||||
case LOAN_PRODUCT, SAVINGS_PRODUCT, SHARE_PRODUCT -> {
|
||||
params.add(appTableId);
|
||||
yield "select null as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, p.id as entityId from "
|
||||
+ entityTable.getName() + " as p WHERE p.id = ?";
|
||||
}
|
||||
default -> throw new PlatformDataIntegrityException("error.msg.invalid.dataScopeCriteria",
|
||||
"Application Table: " + entityTable.getName() + " not catered for in data Scoping");
|
||||
};
|
||||
}
|
||||
|
||||
public String getOfficeJoinCondition(String officeHierarchy, String joinTableAlias) {
|
||||
return " join m_office o on o.id = " + joinTableAlias + ".office_id and o.hierarchy like '" + officeHierarchy + "%' ";
|
||||
public String getOfficeJoinCondition(String joinTableAlias) {
|
||||
return " join m_office o on o.id = " + joinTableAlias + ".office_id and o.hierarchy like ? ";
|
||||
}
|
||||
|
||||
public String getGroupOfficeJoinCondition(String officeHierarchy, String appTableAlias) {
|
||||
return " join m_group g on g.id = " + appTableAlias + ".group_id " + getOfficeJoinCondition(officeHierarchy, "g");
|
||||
public String getGroupOfficeJoinCondition(String appTableAlias) {
|
||||
return " join m_group g on g.id = " + appTableAlias + ".group_id " + getOfficeJoinCondition("g");
|
||||
}
|
||||
|
||||
public String getClientOfficeJoinCondition(String officeHierarchy, String appTableAlias) {
|
||||
return " join m_client c on c.id = " + appTableAlias + ".client_id " + getOfficeJoinCondition(officeHierarchy, "c");
|
||||
public String getClientOfficeJoinCondition(String appTableAlias) {
|
||||
return " join m_client c on c.id = " + appTableAlias + ".client_id " + getOfficeJoinCondition("c");
|
||||
}
|
||||
|
||||
public GenericResultsetData retrieveDataTableGenericResultSet(final EntityTables entityTable, final String dataTableName,
|
||||
@@ -201,20 +231,21 @@ public class DatatableUtil {
|
||||
final List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(dataTableName);
|
||||
final boolean multiRow = isMultirowDatatable(columnHeaders);
|
||||
|
||||
String whereClause = getFKField(entityTable) + " = " + appTableId;
|
||||
sqlValidator.validate(whereClause);
|
||||
String sql = "select * from " + sqlGenerator.escape(dataTableName) + " where " + whereClause;
|
||||
final List<Object> params = new ArrayList<>();
|
||||
params.add(appTableId);
|
||||
String sql = "select * from " + sqlGenerator.escape(dataTableName) + " where " + getFKField(entityTable) + " = ?";
|
||||
|
||||
// id only used for reading a specific entry that belongs to appTableId (in a one to many datatable)
|
||||
if (multiRow && id != null) {
|
||||
sql = sql + " and " + TABLE_FIELD_ID + " = " + id;
|
||||
sql = sql + " and " + TABLE_FIELD_ID + " = ?";
|
||||
params.add(id);
|
||||
}
|
||||
if (StringUtils.isNotBlank(order)) {
|
||||
columnValidator.validateSqlInjection(sql, order);
|
||||
sql = sql + " order by " + order;
|
||||
}
|
||||
|
||||
final List<ResultsetRowData> result = genericDataService.fillResultsetRowData(sql, columnHeaders);
|
||||
final List<ResultsetRowData> result = genericDataService.fillResultsetRowData(sql, columnHeaders, params.toArray());
|
||||
return new GenericResultsetData(columnHeaders, result);
|
||||
}
|
||||
|
||||
|
||||
+41
-62
@@ -152,9 +152,7 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
@Override
|
||||
public void registerDatatable(final String dataTableName, final String entityName, final String entitySubType) {
|
||||
Integer category = DataTableApiConstant.CATEGORY_DEFAULT;
|
||||
|
||||
final String permissionSql = this.getPermissionSql(dataTableName);
|
||||
this.registerDataTable(entityName, dataTableName, entitySubType, category, permissionSql);
|
||||
this.registerDataTable(entityName, dataTableName, entitySubType, category);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -167,13 +165,13 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
Integer category = this.getCategory(command);
|
||||
|
||||
this.dataTableValidator.validateDataTableRegistration(command.json());
|
||||
final String permissionSql = this.getPermissionSql(dataTableName);
|
||||
this.registerDataTable(applicationTableName, dataTableName, entitySubType, category, permissionSql);
|
||||
this.registerDataTable(applicationTableName, dataTableName, entitySubType, category);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void registerDatatable(final JsonCommand command, final String permissionSql) {
|
||||
// permissionSql parameter is kept for interface compatibility but ignored in favor of parameterized queries
|
||||
final String applicationTableName = datatableReadService.getTableName(command.getUrl());
|
||||
final String dataTableName = datatableReadService.getDataTableName(command.getUrl());
|
||||
final String entitySubType = command.stringValueOfParameterNamed(ENTITY_SUB_TYPE);
|
||||
@@ -182,30 +180,23 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
|
||||
this.dataTableValidator.validateDataTableRegistration(command.json());
|
||||
|
||||
this.registerDataTable(applicationTableName, dataTableName, entitySubType, category, permissionSql);
|
||||
this.registerDataTable(applicationTableName, dataTableName, entitySubType, category);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void deregisterDatatable(final String datatable) {
|
||||
datatableUtil.validateDatatableRegistered(datatable);
|
||||
final String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_"
|
||||
+ datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')";
|
||||
final Object[] permissionCodes = { "CREATE_" + datatable, "CREATE_" + datatable + "_CHECKER", "READ_" + datatable,
|
||||
"UPDATE_" + datatable, "UPDATE_" + datatable + "_CHECKER", "DELETE_" + datatable, "DELETE_" + datatable + "_CHECKER" };
|
||||
final String placeholders = "(?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
final String deleteRolePermissionsSql = "delete from m_role_permission where m_role_permission.permission_id in (select id from m_permission where code in "
|
||||
+ permissionList + ")";
|
||||
|
||||
final String deletePermissionsSql = "delete from m_permission where code in " + permissionList;
|
||||
final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'";
|
||||
final String deleteFromConfigurationSql = "delete from c_configuration where name ='" + datatable + "'";
|
||||
|
||||
String[] sqlArray = new String[4];
|
||||
sqlArray[0] = deleteRolePermissionsSql;
|
||||
sqlArray[1] = deletePermissionsSql;
|
||||
sqlArray[2] = deleteRegisteredDatatableSql;
|
||||
sqlArray[3] = deleteFromConfigurationSql;
|
||||
|
||||
this.jdbcTemplate.batchUpdate(sqlArray); // NOSONAR
|
||||
this.jdbcTemplate
|
||||
.update("DELETE FROM m_role_permission WHERE m_role_permission.permission_id IN (SELECT id FROM m_permission WHERE code IN "
|
||||
+ placeholders + ")", permissionCodes);
|
||||
this.jdbcTemplate.update("DELETE FROM m_permission WHERE code IN " + placeholders, permissionCodes);
|
||||
this.jdbcTemplate.update("DELETE FROM x_registered_table WHERE registered_table_name = ?", datatable);
|
||||
this.jdbcTemplate.update("DELETE FROM c_configuration WHERE name = ?", datatable);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -605,8 +596,8 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
return deleteDatatableEntries(dataTableName, appTableId, datatableId, command);
|
||||
}
|
||||
|
||||
private void registerDataTable(final String entityName, final String dataTableName, final String entitySubType, final Integer category,
|
||||
final String permissionsSql) {
|
||||
private void registerDataTable(final String entityName, final String dataTableName, final String entitySubType,
|
||||
final Integer category) {
|
||||
datatableUtil.resolveEntity(entityName);
|
||||
datatableUtil.validateDatatableName(dataTableName);
|
||||
validateDataTableExists(dataTableName);
|
||||
@@ -622,7 +613,7 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
|
||||
try {
|
||||
this.namedParameterJdbcTemplate.update(registerDatatableSql, paramMap);
|
||||
this.jdbcTemplate.update(permissionsSql);
|
||||
this.registerPermissions(dataTableName, true);
|
||||
|
||||
// add the registered table to the config if it is a ppi
|
||||
if (category.equals(DataTableApiConstant.CATEGORY_PPI)) {
|
||||
@@ -638,24 +629,21 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
}
|
||||
}
|
||||
|
||||
private String getPermissionSql(final String dataTableName) {
|
||||
final String createPermission = "'CREATE_" + dataTableName + "'";
|
||||
final String createPermissionChecker = "'CREATE_" + dataTableName + "_CHECKER'";
|
||||
final String readPermission = "'READ_" + dataTableName + "'";
|
||||
final String updatePermission = "'UPDATE_" + dataTableName + "'";
|
||||
final String updatePermissionChecker = "'UPDATE_" + dataTableName + "_CHECKER'";
|
||||
final String deletePermission = "'DELETE_" + dataTableName + "'";
|
||||
final String deletePermissionChecker = "'DELETE_" + dataTableName + "_CHECKER'";
|
||||
private void registerPermissions(final String dataTableName, final boolean canMakerChecker) {
|
||||
final List<String> escapedColumns = Stream.of("grouping", "code", "action_name", "entity_name", "can_maker_checker")
|
||||
.map(sqlGenerator::escape).toList();
|
||||
final String columns = String.join(", ", escapedColumns);
|
||||
|
||||
return "insert into m_permission (" + columns + ") values " + "('datatable', " + createPermission + ", 'CREATE', '" + dataTableName
|
||||
+ "', true)," + "('datatable', " + createPermissionChecker + ", 'CREATE', '" + dataTableName + "', false),"
|
||||
+ "('datatable', " + readPermission + ", 'READ', '" + dataTableName + "', false)," + "('datatable', " + updatePermission
|
||||
+ ", 'UPDATE', '" + dataTableName + "', true)," + "('datatable', " + updatePermissionChecker + ", 'UPDATE', '"
|
||||
+ dataTableName + "', false)," + "('datatable', " + deletePermission + ", 'DELETE', '" + dataTableName + "', true),"
|
||||
+ "('datatable', " + deletePermissionChecker + ", 'DELETE', '" + dataTableName + "', false)";
|
||||
final String sql = "INSERT INTO m_permission (" + columns + ") VALUES ('datatable', ?, ?, ?, ?)";
|
||||
final Object[][] permissions = { { "CREATE_" + dataTableName, "CREATE", dataTableName, canMakerChecker },
|
||||
{ "CREATE_" + dataTableName + "_CHECKER", "CREATE", dataTableName, false },
|
||||
{ "READ_" + dataTableName, "READ", dataTableName, false },
|
||||
{ "UPDATE_" + dataTableName, "UPDATE", dataTableName, canMakerChecker },
|
||||
{ "UPDATE_" + dataTableName + "_CHECKER", "UPDATE", dataTableName, false },
|
||||
{ "DELETE_" + dataTableName, "DELETE", dataTableName, canMakerChecker },
|
||||
{ "DELETE_" + dataTableName + "_CHECKER", "DELETE", dataTableName, false }, };
|
||||
for (Object[] params : permissions) {
|
||||
this.jdbcTemplate.update(sql, params);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getCategory(final JsonCommand command) {
|
||||
@@ -897,9 +885,9 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
String schemaSql = databaseTypeResolver.isMySQL() ? "i.TABLE_SCHEMA = SCHEMA()"
|
||||
: "i.table_catalog = current_catalog AND i.table_schema = current_schema";
|
||||
String findFKSql = "SELECT count(*) FROM information_schema.TABLE_CONSTRAINTS i" + " WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND "
|
||||
+ schemaSql + " AND i.TABLE_NAME = '" + datatableName + "' AND i.CONSTRAINT_NAME = '" + fkName + "' ";
|
||||
+ schemaSql + " AND i.TABLE_NAME = ? AND i.CONSTRAINT_NAME = ?";
|
||||
|
||||
final Integer count = this.jdbcTemplate.queryForObject(findFKSql, Integer.class); // NOSONAR
|
||||
final Integer count = this.jdbcTemplate.queryForObject(findFKSql, Integer.class, datatableName, fkName); // NOSONAR
|
||||
if (count != null && count > 0) {
|
||||
codeMappings.add(datatableAlias + "_" + name);
|
||||
constrainBuilder.append(", DROP FOREIGN KEY ").append(sqlGenerator.escape(fkName)).append(" ");
|
||||
@@ -908,36 +896,28 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
|
||||
private void registerColumnCodeMapping(final Map<String, Long> codeMappings) {
|
||||
if (codeMappings != null && !codeMappings.isEmpty()) {
|
||||
final String[] addSqlList = new String[codeMappings.size()];
|
||||
int i = 0;
|
||||
final String sql = "INSERT INTO x_table_column_code_mappings (column_alias_name, code_id) VALUES (?, ?)";
|
||||
for (final Map.Entry<String, Long> mapEntry : codeMappings.entrySet()) {
|
||||
addSqlList[i++] = "insert into x_table_column_code_mappings (column_alias_name, code_id) values ('" + mapEntry.getKey()
|
||||
+ "'," + mapEntry.getValue() + ");";
|
||||
this.jdbcTemplate.update(sql, mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
|
||||
this.jdbcTemplate.batchUpdate(addSqlList);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteColumnCodeMapping(final List<String> columnNames) {
|
||||
if (columnNames != null && !columnNames.isEmpty()) {
|
||||
final String[] deleteSqlList = new String[columnNames.size()];
|
||||
int i = 0;
|
||||
final String sql = "DELETE FROM x_table_column_code_mappings WHERE column_alias_name = ?";
|
||||
for (final String columnName : columnNames) {
|
||||
deleteSqlList[i++] = "DELETE FROM x_table_column_code_mappings WHERE column_alias_name='" + columnName + "';";
|
||||
this.jdbcTemplate.update(sql, columnName);
|
||||
}
|
||||
|
||||
this.jdbcTemplate.batchUpdate(deleteSqlList);
|
||||
}
|
||||
}
|
||||
|
||||
private int getCodeIdForColumn(final String dataTableNameAlias, final String name) {
|
||||
final StringBuilder checkColumnCodeMapping = new StringBuilder();
|
||||
checkColumnCodeMapping.append("select ccm.code_id from x_table_column_code_mappings ccm where ccm.column_alias_name='")
|
||||
.append(dataTableNameAlias).append("_").append(name).append("'");
|
||||
final String sql = "SELECT ccm.code_id FROM x_table_column_code_mappings ccm WHERE ccm.column_alias_name = ?";
|
||||
final String columnAliasName = dataTableNameAlias + "_" + name;
|
||||
Integer codeId = 0;
|
||||
try {
|
||||
codeId = this.jdbcTemplate.queryForObject(checkColumnCodeMapping.toString(), Integer.class);
|
||||
codeId = this.jdbcTemplate.queryForObject(sql, Integer.class, columnAliasName);
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
log.warn("Error occurred.", e);
|
||||
}
|
||||
@@ -1327,10 +1307,9 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
whereColumn = TABLE_FIELD_ID;
|
||||
whereValue = datatableId;
|
||||
}
|
||||
String sql = "DELETE FROM " + sqlGenerator.escape(dataTableName) + " WHERE " + sqlGenerator.escape(whereColumn) + " = "
|
||||
+ whereValue;
|
||||
String sql = "DELETE FROM " + sqlGenerator.escape(dataTableName) + " WHERE " + sqlGenerator.escape(whereColumn) + " = ?";
|
||||
|
||||
this.jdbcTemplate.update(sql); // NOSONAR
|
||||
this.jdbcTemplate.update(sql, whereValue); // NOSONAR
|
||||
final Map<String, Object> dataParams = null;
|
||||
final DatatableEntryDetails datatableEntryDetails = new DatatableEntryDetails(dataTableName, entityTable, datatableId, appTableId,
|
||||
dataParams);
|
||||
@@ -1351,8 +1330,8 @@ public class DatatableWriteServiceImpl implements DatatableWriteService {
|
||||
private boolean isDatatableAttachedToEntityDatatableCheck(final String datatableName) {
|
||||
String sql = "SELECT COUNT(edc.x_registered_table_name) FROM x_registered_table xrt"
|
||||
+ " JOIN m_entity_datatable_check edc ON edc.x_registered_table_name = xrt.registered_table_name"
|
||||
+ " WHERE edc.x_registered_table_name = '" + datatableName + "'";
|
||||
final Long count = this.jdbcTemplate.queryForObject(sql, Long.class); // NOSONAR
|
||||
+ " WHERE edc.x_registered_table_name = ?";
|
||||
final Long count = this.jdbcTemplate.queryForObject(sql, Long.class, datatableName); // NOSONAR
|
||||
return count != null && count > 0;
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -141,6 +141,13 @@ public class GenericDataServiceImpl implements GenericDataService {
|
||||
return fillResultsetRowData(rs, columnHeaders);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<ResultsetRowData> fillResultsetRowData(final String sql, List<ResultsetColumnHeaderData> columnHeaders, Object... args) {
|
||||
final SqlRowSet rs = jdbcTemplate.queryForRowSet(sql, args);
|
||||
return fillResultsetRowData(rs, columnHeaders);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static List<ResultsetRowData> fillResultsetRowData(SqlRowSet rs, List<ResultsetColumnHeaderData> columnHeaders) {
|
||||
final SqlRowSetMetaData rsmd = rs.getMetaData();
|
||||
|
||||
+1
-30
@@ -18,13 +18,9 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.survey.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -36,39 +32,14 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class WriteSurveyServiceImpl implements WriteSurveyService {
|
||||
|
||||
private final DatatableReadService datatableReadService;
|
||||
private final DatatableWriteService datatableWriteService;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult registerSurvey(JsonCommand command) {
|
||||
final String dataTableName = datatableReadService.getDataTableName(command.getUrl());
|
||||
final String permissionSql = this.getPermissionSql(dataTableName);
|
||||
datatableWriteService.registerDatatable(command, permissionSql);
|
||||
datatableWriteService.registerDatatable(command);
|
||||
|
||||
return CommandProcessingResult.commandOnlyResult(command.commandId());
|
||||
|
||||
}
|
||||
|
||||
private String getPermissionSql(final String dataTableName) {
|
||||
final String createPermission = "'CREATE_" + dataTableName + "'";
|
||||
final String createPermissionChecker = "'CREATE_" + dataTableName + "_CHECKER'";
|
||||
final String readPermission = "'READ_" + dataTableName + "'";
|
||||
final String updatePermission = "'UPDATE_" + dataTableName + "'";
|
||||
final String updatePermissionChecker = "'UPDATE_" + dataTableName + "_CHECKER'";
|
||||
final String deletePermission = "'DELETE_" + dataTableName + "'";
|
||||
final String deletePermissionChecker = "'DELETE_" + dataTableName + "_CHECKER'";
|
||||
final List<String> escapedColumns = Stream.of("grouping", "code", "action_name", "entity_name", "can_maker_checker")
|
||||
.map(sqlGenerator::escape).toList();
|
||||
final String columns = String.join(", ", escapedColumns);
|
||||
|
||||
return "insert into m_permission (" + columns + ") values " + "('datatable', " + createPermission + ", 'CREATE', '" + dataTableName
|
||||
+ "', false)," + "('datatable', " + createPermissionChecker + ", 'CREATE', '" + dataTableName + "', false),"
|
||||
+ "('datatable', " + readPermission + ", 'READ', '" + dataTableName + "', false)," + "('datatable', " + updatePermission
|
||||
+ ", 'UPDATE', '" + dataTableName + "', false)," + "('datatable', " + updatePermissionChecker + ", 'UPDATE', '"
|
||||
+ dataTableName + "', false)," + "('datatable', " + deletePermission + ", 'DELETE', '" + dataTableName + "', false),"
|
||||
+ "('datatable', " + deletePermissionChecker + ", 'DELETE', '" + dataTableName + "', false)";
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
+3
@@ -134,6 +134,9 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
final PaginationParameters paginationParameters) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues,
|
||||
depositAccountType.resourceName());
|
||||
|
||||
final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
|
||||
if (depositAccountMapper == null) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user