diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index cbb612a7a..fac9c6e39 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -204,17 +204,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } @Override - public List queryDataTable(String datatable, String columnFilter, String valueFilter, String resultColumns) { + public List queryDataTable(String datatable, final String columnFilter, String valueFilter, String resultColumns) { Arrays.asList(datatable, columnFilter, valueFilter, resultColumns).forEach(SQLInjectionValidator::validateDynamicQuery); List 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; - String filterColumnType = resultsetColumnHeaderData.stream().filter(column -> Objects.equals(columnFilter, column.getColumnName())) - .findFirst().map(ResultsetColumnHeaderData::getColumnType).orElse(columnFilter + " does not exist in datatable"); 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); @@ -234,6 +236,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ private void extractResults(SqlRowSet rowSet, String[] resultColumnNames, List results) { JsonObject json = new JsonObject(); for (String rcn : resultColumnNames) { + rcn = rcn.replaceAll("\"", ""); Object rowValue = rowSet.getObject(rcn); if (rowValue != null) { if (rowValue instanceof Character) { @@ -332,6 +335,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ return jdbcTemplate.queryForRowSet(sql, new Object[] { finalValueFilter }, argType); } + private String escapeFieldNames(final String inputFields) { + final String delimiter = ","; + if (StringUtils.isBlank(inputFields)) { + return inputFields; + } + final String[] fieldList = StringUtils.split(inputFields, delimiter); + final String[] outputFields = new String[fieldList.length]; + for (int i = 0; i < fieldList.length; i++) { + outputFields[i] = sqlGenerator.escape(fieldList[i].trim()); + } + return String.join(",", outputFields); + } + private static void validateRequestParams(String columnFilter, String valueFilter, String resultColumns, List resultsetColumnHeaderData) { List paramErrors = new ArrayList<>(); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java index cd0bec1df..b3d6b96a8 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java @@ -27,6 +27,7 @@ import com.google.gson.JsonObject; import java.util.Collections; import java.util.List; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver; import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData; import org.apache.fineract.infrastructure.security.utils.SQLInjectionException; @@ -52,6 +53,9 @@ public class ReadWriteNonCoreDataServiceImplTest { @Mock private DatabaseTypeResolver databaseTypeResolver; + @Mock + private DatabaseSpecificSQLGenerator sqlGenerator; + @InjectMocks private ReadWriteNonCoreDataServiceImpl underTest; @@ -81,6 +85,7 @@ public class ReadWriteNonCoreDataServiceImplTest { .thenReturn(sqlRS); when(sqlRS.next()).thenReturn(true).thenReturn(false); when(sqlRS.getObject(ArgumentMatchers.anyString())).thenReturn("value1").thenReturn("value2"); + when(sqlGenerator.escape(ArgumentMatchers.anyString())).thenReturn("rc1").thenReturn("rc2").thenReturn("table").thenReturn("cf1"); when(databaseTypeResolver.isPostgreSQL()).thenReturn(true); ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", "text", 10L, false, false, null, null, false, false); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java index 526bb9469..c8c6c4bc7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java @@ -158,6 +158,12 @@ public class DatatableHelper extends IntegrationTest { return Utils.performServerDelete(this.requestSpec, this.responseSpec, deleteEntryUrl, jsonAttributeToGetBack); } + public String runDatatableQuery(final String datatableName, final String columnFilter, final String valueFilter, + final String resultColumns) { + return Utils.performServerGet(this.requestSpec, this.responseSpec, DATATABLE_URL + "/" + datatableName + "/query" + "?columnFilter=" + + columnFilter + "&valueFilter=" + valueFilter + "&resultColumns=" + resultColumns + "&" + Utils.TENANT_IDENTIFIER); + } + public static void verifyDatatableCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String generatedDatatableName) { LOG.info("------------------------------CHECK DATATABLE DETAILS------------------------------------\n"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java index ad8122047..09ca16a77 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java @@ -309,6 +309,108 @@ public class DatatableIntegrationTest extends IntegrationTest { assertEquals(datatableName, deletedDataTableName, "ERROR IN DELETING THE DATATABLE"); } + @Test + public void validateCreateReadDeleteDatatableWithCaseSensitive() throws ParseException { + + // creating datatable for client entity + final HashMap columnMap = new HashMap<>(); + final List> datatableColumnsList = new ArrayList<>(); + columnMap.put("datatableName", Utils.uniqueRandomStringGenerator(CLIENT_APP_TABLE_NAME + "_", 5)); + columnMap.put("apptableName", CLIENT_APP_TABLE_NAME); + columnMap.put("entitySubType", "PERSON"); + columnMap.put("multiRow", false); + String itsADate = "itsADate"; + String itsADecimal = "itsADecimal"; + String itsAString = "itsAString"; + String dateFormat = "dateFormat"; + + DatatableHelper.addDatatableColumns(datatableColumnsList, itsADate, "Date", true, null, null); + DatatableHelper.addDatatableColumns(datatableColumnsList, itsADecimal, "Decimal", true, null, null); + DatatableHelper.addDatatableColumns(datatableColumnsList, itsAString, "String", true, 10, null); + columnMap.put("columns", datatableColumnsList); + String datatabelRequestJsonString = new Gson().toJson(columnMap); + LOG.info("map : {}", datatabelRequestJsonString); + + HashMap datatableResponse = this.datatableHelper.createDatatable(datatabelRequestJsonString, ""); + String datatableName = (String) datatableResponse.get("resourceIdentifier"); + DatatableHelper.verifyDatatableCreatedOnServer(this.requestSpec, this.responseSpec, datatableName); + + // creating client with datatables + final Integer clientID = ClientHelper.createClientAsPerson(requestSpec, responseSpec); + + // creating new client datatable entry + final boolean genericResultSet = true; + + final HashMap datatableEntryMap = new HashMap<>(); + datatableEntryMap.put(itsADate, Utils.randomDateGenerator("yyyy-MM-dd")); + datatableEntryMap.put(itsADecimal, Utils.randomDecimalGenerator(4, 3)); + datatableEntryMap.put(itsAString, Utils.randomStringGenerator("", 8)); + datatableEntryMap.put("locale", "en"); + datatableEntryMap.put(dateFormat, "yyyy-MM-dd"); + + String datatabelEntryRequestJsonString = new Gson().toJson(datatableEntryMap); + LOG.info("map : {}", datatabelEntryRequestJsonString); + + HashMap datatableEntryResponse = this.datatableHelper.createDatatableEntry(datatableName, clientID, + genericResultSet, datatabelEntryRequestJsonString); + assertNotNull(datatableEntryResponse.get("resourceId"), "ERROR IN CREATING THE ENTITY DATATABLE RECORD"); + + // Read the Datatable entry generated with genericResultSet in true (default) + final HashMap items = this.datatableHelper.readDatatableEntry(datatableName, clientID, genericResultSet, + (Integer) datatableEntryResponse.get("resourceId"), ""); + assertNotNull(items); + assertEquals(1, ((List) items.get("data")).size()); + + assertEquals("client_id", ((Map) ((List) items.get("columnHeaders")).get(0)).get("columnName")); + assertEquals(clientID, ((List) ((Map) ((List) items.get("data")).get(0)).get("row")).get(0)); + + assertEquals(itsADate, ((Map) ((List) items.get("columnHeaders")).get(1)).get("columnName")); + assertEquals(datatableEntryMap.get(itsADate), + Utils.arrayDateToString((List) ((List) ((Map) ((List) items.get("data")).get(0)).get("row")).get(1))); + + assertEquals(itsADecimal, ((Map) ((List) items.get("columnHeaders")).get(2)).get("columnName")); + assertEquals(datatableEntryMap.get(itsADecimal), ((List) ((Map) ((List) items.get("data")).get(0)).get("row")).get(2)); + + assertEquals(itsAString, ((Map) ((List) items.get("columnHeaders")).get(3)).get("columnName")); + assertEquals(datatableEntryMap.get(itsAString), ((List) ((Map) ((List) items.get("data")).get(0)).get("row")).get(3)); + + // Update datatable entry + final String randomValue = Utils.randomStringGenerator("", 8); + datatableEntryMap.put(itsADate, Utils.randomDateGenerator("yyyy-MM-dd")); + datatableEntryMap.put(itsADecimal, Utils.randomDecimalGenerator(4, 3)); + datatableEntryMap.put(itsAString, randomValue); + + datatableEntryMap.put("locale", "en"); + datatableEntryMap.put(dateFormat, "yyyy-MM-dd"); + + datatabelEntryRequestJsonString = new Gson().toJson(datatableEntryMap); + LOG.info("map : {}", datatabelEntryRequestJsonString); + + HashMap updatedDatatableEntryResponse = this.datatableHelper.updateDatatableEntry(datatableName, clientID, false, + datatabelEntryRequestJsonString); + + assertEquals(clientID, updatedDatatableEntryResponse.get("clientId")); + + assertEquals(datatableEntryMap.get(itsADate), + Utils.arrayDateToString((List) ((Map) updatedDatatableEntryResponse.get("changes")).get(itsADate))); + assertEquals(datatableEntryMap.get(itsADecimal), ((Map) updatedDatatableEntryResponse.get("changes")).get(itsADecimal)); + assertEquals(datatableEntryMap.get(itsAString), ((Map) updatedDatatableEntryResponse.get("changes")).get(itsAString)); + + // Read the datatable with a query + LOG.info("query in {} for value : {}", itsAString, randomValue); + final String queryResult = this.datatableHelper.runDatatableQuery(datatableName, itsAString, randomValue, "client_id,itsADecimal"); + assertNotNull(queryResult); + LOG.info("query result : {}", queryResult); + + // deleting datatable entries + Integer appTableId = this.datatableHelper.deleteDatatableEntries(datatableName, clientID, "clientId"); + assertEquals(clientID, appTableId, "ERROR IN DELETING THE DATATABLE ENTRIES"); + + // deleting the datatable + String deletedDataTableName = this.datatableHelper.deleteDatatable(datatableName); + assertEquals(datatableName, deletedDataTableName, "ERROR IN DELETING THE DATATABLE"); + } + @Test public void validateInsertNullValues() { // Fetch / Create TST code