FINERACT-1724: Datatables query API fix - invalid query filter
This commit is contained in:
committed by
Adam Saghy
parent
d639b20af1
commit
31f41558fc
+27
-105
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.infrastructure.dataqueries.service;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Locale.ENGLISH;
|
||||
import static org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterErrorWithValue;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -33,18 +34,14 @@ import java.sql.Date;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.PersistenceException;
|
||||
@@ -84,7 +81,6 @@ import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventer
|
||||
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
@@ -113,21 +109,15 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
.put("number", "INT").put("boolean", "boolean").put("decimal", "DECIMAL").put("date", "DATE").put("datetime", "TIMESTAMP")
|
||||
.put("text", "TEXT").put("dropdown", "INT").build();
|
||||
|
||||
private static final List<String> stringDataTypes = Arrays.asList("char", "varchar", "blob", "text", "tinyblob", "tinytext",
|
||||
"mediumblob", "mediumtext", "longblob", "longtext");
|
||||
private static final DateTimeFormatter DATA_TABLE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter DATA_TABLE_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
public static final String PG_BOOLEAN_TYPE = "boolean";
|
||||
public static final String PG_BOOL_TYPE = "bool";
|
||||
public static final String PG_INT_TYPE = "integer";
|
||||
public static final String PG_BIGINT_TYPE = "bigint";
|
||||
public static final String PG_DATE_TYPE = "date";
|
||||
public static final String PG_NUMERIC_TYPE = "numeric";
|
||||
public static final String PG_TEXT_TYPE = "text";
|
||||
public static final String PG_VARCHAR_TYPE = "character varying";
|
||||
public static final String BIT_TYPE = "bit";
|
||||
private static final List<String> stringDataTypes = asList("char", "varchar", "blob", "text", "tinyblob", "tinytext", "mediumblob",
|
||||
"mediumtext", "longblob", "longtext");
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd";
|
||||
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
private static final DateTimeFormatter DATA_TABLE_DATE_FORMAT = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
private static final DateTimeFormatter DATA_TABLE_DATETIME_FORMAT = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
|
||||
|
||||
public static final String MYSQL_DATETIME_TYPE = "datetime";
|
||||
public static final String MYSQL_DATE_TYPE = "date";
|
||||
public static final String POSTGRES_TIMESTAMP_TYPE = "timestamp without time zone";
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DatabaseTypeResolver databaseTypeResolver;
|
||||
@@ -205,24 +195,15 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
|
||||
@Override
|
||||
public List<JsonObject> queryDataTable(String datatable, final String columnFilter, String valueFilter, String resultColumns) {
|
||||
Arrays.asList(datatable, columnFilter, valueFilter, resultColumns).forEach(SQLInjectionValidator::validateDynamicQuery);
|
||||
asList(datatable, columnFilter, valueFilter, resultColumns).forEach(SQLInjectionValidator::validateDynamicQuery);
|
||||
|
||||
List<ResultsetColumnHeaderData> resultsetColumnHeaderData = genericDataService.fillResultsetColumnHeaders(datatable);
|
||||
validateRequestParams(columnFilter, valueFilter, resultColumns, resultsetColumnHeaderData);
|
||||
String filterColumnType = resultsetColumnHeaderData.stream().filter(column -> Objects.equals(columnFilter, column.getColumnName()))
|
||||
.findFirst().map(ResultsetColumnHeaderData::getColumnType).orElse(columnFilter + " does not exist in datatable");
|
||||
|
||||
String sql = "select " + resultColumns + " from " + datatable + " where " + columnFilter + " = ?";
|
||||
SqlRowSet rowSet = null;
|
||||
if (databaseTypeResolver.isPostgreSQL()) {
|
||||
sql = "select " + escapeFieldNames(resultColumns) + " from " + sqlGenerator.escape(datatable) + " where "
|
||||
+ escapeFieldNames(columnFilter) + " = ?";
|
||||
rowSet = callFilteredPgSql(sql, valueFilter, filterColumnType);
|
||||
} else if (databaseTypeResolver.isMySQL()) {
|
||||
rowSet = callFilteredMysql(sql, valueFilter, filterColumnType);
|
||||
} else {
|
||||
throw new IllegalStateException("Database type is not supported");
|
||||
}
|
||||
Object validatedValueFilter = getValidatedValueFilter(columnFilter, valueFilter, resultsetColumnHeaderData);
|
||||
String sql = "select " + escapeFieldNames(resultColumns) + " from " + sqlGenerator.escape(datatable) + " where "
|
||||
+ escapeFieldNames(columnFilter) + " = ?";
|
||||
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, validatedValueFilter);
|
||||
|
||||
String[] resultColumnNames = resultColumns.split(",");
|
||||
List<JsonObject> results = new ArrayList<>();
|
||||
@@ -233,6 +214,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
return results;
|
||||
}
|
||||
|
||||
private Object getValidatedValueFilter(String columnFilter, String valueFilter,
|
||||
List<ResultsetColumnHeaderData> resultsetColumnHeaderData) {
|
||||
ResultsetColumnHeaderData columnFilterHeader = resultsetColumnHeaderData.stream()
|
||||
.filter(headerData -> headerData.getColumnName().equals(columnFilter)).findAny().orElseThrow();
|
||||
if (MYSQL_DATETIME_TYPE.equals(columnFilterHeader.getColumnType())
|
||||
|| POSTGRES_TIMESTAMP_TYPE.equals(columnFilterHeader.getColumnType())) {
|
||||
return validateColumn(columnFilterHeader, valueFilter, DATE_TIME_FORMAT, ENGLISH);
|
||||
} else {
|
||||
return validateColumn(columnFilterHeader, valueFilter, DATE_FORMAT, ENGLISH);
|
||||
}
|
||||
}
|
||||
|
||||
private void extractResults(SqlRowSet rowSet, String[] resultColumnNames, List<JsonObject> results) {
|
||||
JsonObject json = new JsonObject();
|
||||
for (String rcn : resultColumnNames) {
|
||||
@@ -264,77 +257,6 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private SqlRowSet callFilteredMysql(String sql, String valueFilter, String filterColumnType) {
|
||||
Object finalValueFilter = valueFilter;
|
||||
SqlRowSet rowSet;
|
||||
if (BIT_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
int[] argType = new int[1];
|
||||
argType[0] = Types.BIT;
|
||||
finalValueFilter = BooleanUtils.toString(BooleanUtils.toBooleanObject(valueFilter), "1", "0", "null");
|
||||
rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { finalValueFilter }, argType);
|
||||
} else if (MYSQL_DATE_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
int[] argType = new int[1];
|
||||
argType[0] = Types.DATE;
|
||||
try {
|
||||
rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { LocalDate.parse(valueFilter, DATA_TABLE_DATE_FORMAT) }, argType);
|
||||
} catch (DateTimeParseException e) {
|
||||
List<ApiParameterError> paramErrors = new ArrayList<>();
|
||||
paramErrors.add(parameterErrorWithValue("400",
|
||||
"Unsupported input type for datatable query! Use format: 'yyyy-MM-dd'. Column filter: " + filterColumnType,
|
||||
"valueFilter", valueFilter));
|
||||
throw new PlatformApiDataValidationException(paramErrors, e);
|
||||
}
|
||||
} else if (MYSQL_DATETIME_TYPE.equals(filterColumnType)) {
|
||||
int[] argType = new int[1];
|
||||
argType[0] = Types.TIMESTAMP;
|
||||
try {
|
||||
rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { LocalDateTime.parse(valueFilter, DATA_TABLE_DATETIME_FORMAT) },
|
||||
argType);
|
||||
} catch (DateTimeParseException e) {
|
||||
List<ApiParameterError> paramErrors = new ArrayList<>();
|
||||
paramErrors.add(parameterErrorWithValue("400",
|
||||
"Unsupported input type for datatable query! Use format: 'yyyy-MM-dd HH:mm:ss'. Column filter: " + filterColumnType,
|
||||
"valueFilter", valueFilter));
|
||||
throw new PlatformApiDataValidationException(paramErrors, e);
|
||||
}
|
||||
} else {
|
||||
rowSet = jdbcTemplate.queryForRowSet(sql, finalValueFilter);
|
||||
}
|
||||
return rowSet;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private SqlRowSet callFilteredPgSql(String sql, String valueFilter, String filterColumnType) {
|
||||
Object finalValueFilter = valueFilter;
|
||||
int[] argType = new int[1];
|
||||
if (BIT_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
finalValueFilter = BooleanUtils.toString(BooleanUtils.toBooleanObject(valueFilter), "1", "0", "null");
|
||||
argType[0] = Types.BIT;
|
||||
} else if (PG_BOOLEAN_TYPE.equalsIgnoreCase(filterColumnType) || PG_BOOL_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
finalValueFilter = BooleanUtils.toString(BooleanUtils.toBooleanObject(valueFilter), "true", "false", "null");
|
||||
argType[0] = Types.BOOLEAN;
|
||||
} else if (PG_INT_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
argType[0] = Types.INTEGER;
|
||||
} else if (PG_BIGINT_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
argType[0] = Types.BIGINT;
|
||||
} else if (PG_DATE_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
argType[0] = Types.DATE;
|
||||
} else if (filterColumnType.toLowerCase().contains("timestamp")) {
|
||||
argType[0] = Types.TIMESTAMP;
|
||||
} else if (PG_NUMERIC_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
argType[0] = Types.DECIMAL;
|
||||
} else if (PG_TEXT_TYPE.equalsIgnoreCase(filterColumnType) || PG_VARCHAR_TYPE.equalsIgnoreCase(filterColumnType)) {
|
||||
argType[0] = Types.VARCHAR;
|
||||
} else {
|
||||
List<ApiParameterError> paramErrors = new ArrayList<>();
|
||||
paramErrors.add(parameterErrorWithValue("400", "Unsupported input type for datatable query! Column filter: " + filterColumnType,
|
||||
"valueFilter", valueFilter));
|
||||
throw new PlatformApiDataValidationException(paramErrors);
|
||||
}
|
||||
return jdbcTemplate.queryForRowSet(sql, new Object[] { finalValueFilter }, argType);
|
||||
}
|
||||
|
||||
private String escapeFieldNames(final String inputFields) {
|
||||
final String delimiter = ",";
|
||||
if (StringUtils.isBlank(inputFields)) {
|
||||
@@ -348,7 +270,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
||||
return String.join(",", outputFields);
|
||||
}
|
||||
|
||||
private static void validateRequestParams(String columnFilter, String valueFilter, String resultColumns,
|
||||
private void validateRequestParams(String columnFilter, String valueFilter, String resultColumns,
|
||||
List<ResultsetColumnHeaderData> resultsetColumnHeaderData) {
|
||||
List<ApiParameterError> paramErrors = new ArrayList<>();
|
||||
List<String> dataTableColumnNames = resultsetColumnHeaderData.stream().map(ResultsetColumnHeaderData::getColumnName).toList();
|
||||
|
||||
Reference in New Issue
Block a user