diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java index bf7c1527f..ccf3f0d99 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/api/ProvisioningEntriesApiResource.java @@ -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 entries = this.provisioningEntriesReadPlatformService.retrieveProvisioningEntries(params); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.entriesApiJsonSerializer.serialize(settings, entries, PROVISIONING_ENTRY_PARAMETERS); diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java index 39818d37c..7873e441f 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java @@ -319,33 +319,33 @@ public class ProvisioningEntriesReadPlatformServiceImpl implements ProvisioningE String whereClose = " where "; List 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()); } } diff --git a/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java b/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java index bd42683dd..283a9a0a3 100644 --- a/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java +++ b/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/api/TellerApiResource.java @@ -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 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 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); diff --git a/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java b/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java index 39c793322..7667fdfba 100644 --- a/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java +++ b/fineract-branch/src/main/java/org/apache/fineract/organisation/teller/data/CashierTransactionDataValidator.java @@ -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(); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 95347b767..f78119b94 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -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 patterns; + private List profiles; + } + + @Getter + @Setter + public static class FineractSqlValidationProfileProperties { + + private String name; + private String description; + private List 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; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java index 4a2eb6bb7..aba479053 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParameters.java @@ -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; -/** - *

- * Immutable data object representing pagination parameter values. - *

- */ -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()); } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java index 856c49be1..f8a17b57f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/PaginationParametersDataValidator.java @@ -34,7 +34,7 @@ public class PaginationParametersDataValidator { public void validateParameterValues(PaginationParameters parameters, final Set supportedOrdeByValues, final String resourceName) { final List 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( diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java index 879d0f4d7..5339bf444 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java @@ -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); - } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/exception/SqlValidationException.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/exception/SqlValidationException.java new file mode 100644 index 000000000..e6c8c787d --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/exception/SqlValidationException.java @@ -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); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/service/SqlValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/service/SqlValidator.java new file mode 100644 index 000000000..113f7d283 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/service/SqlValidator.java @@ -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; +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java index cf5c2ee86..f16ecd689 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/ColumnValidator.java @@ -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> 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 operator = new ArrayList<>(Arrays.asList("=", ">", "<", "> =", "< =", "! =", "!=", ">=", "<=")); condition = condition.trim().replace("( ", "(").replace(" )", ")").toLowerCase(); for (String op : operator) { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/DefaultSqlValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/DefaultSqlValidator.java new file mode 100644 index 000000000..311473bf8 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/DefaultSqlValidator.java @@ -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 patterns = new LinkedHashMap<>(); + private Map 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())); + } + } + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLBuilder.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLBuilder.java index 999f75258..283e53bef 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLBuilder.java @@ -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. * *

- * 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 */ diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionException.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionException.java index 3170bf8d8..36ff0d7fb 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionException.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionException.java @@ -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() { diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java deleted file mode 100644 index 74c540b38..000000000 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java +++ /dev/null @@ -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 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 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 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 commandsList, SQLCommandCondition condition) { - for (String[] commands : commandsList) { - validateSQLCommand(lowerCaseSQL, commands, condition); - } - } -} diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java index 643e8f575..b52fedfb6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/search/data/AdHocQueryDataValidator.java @@ -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 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; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java index d8dcbba8e..5a2d9c5ad 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java @@ -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 mapHeadersToName(@NotNull Collection columnHeaders) { + public Map mapHeadersToName(@NotNull Collection columnHeaders) { return columnHeaders.stream().collect(Collectors.toMap(ResultsetColumnHeaderData::getColumnName, e -> e)); } - public static ResultsetColumnHeaderData findFiltered(@NotNull Collection columnHeaders, + public ResultsetColumnHeaderData findFiltered(@NotNull Collection columnHeaders, @NotNull Predicate filter) { return columnHeaders.stream().filter(filter).findFirst().orElse(null); } - public static ResultsetColumnHeaderData getFiltered(@NotNull Collection columnHeaders, + public ResultsetColumnHeaderData getFiltered(@NotNull Collection columnHeaders, @NotNull Predicate 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 selectColumns, - @NotNull List resultColumns, @NotNull List results) { + public void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List selectColumns, @NotNull List resultColumns, + @NotNull List 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 validateToJdbcColumnNames(List columns, Map headersByName, + public List validateToJdbcColumnNames(List columns, Map headersByName, boolean allowEmpty) { List columnHeaders = validateToJdbcColumns(columns, headersByName, allowEmpty); return columnHeaders.stream().map(e -> e == null ? null : e.getColumnName()).toList(); } @NotNull - public static List validateToJdbcColumns(List columns, - Map headersByName, boolean allowEmpty) { + public List validateToJdbcColumns(List columns, Map headersByName, + boolean allowEmpty) { final List errors = new ArrayList<>(); List result = new ArrayList<>(); @@ -140,13 +146,13 @@ public final class SearchUtil { return result; } - public static String validateToJdbcColumnName(String column, Map headersByName, boolean allowEmpty) { + public String validateToJdbcColumnName(String column, Map headersByName, boolean allowEmpty) { ResultsetColumnHeaderData columnHeader = validateToJdbcColumn(column, headersByName, allowEmpty); return columnHeader == null ? null : columnHeader.getColumnName(); } - public static ResultsetColumnHeaderData validateToJdbcColumn(String column, - @NotNull Map headersByName, boolean allowEmpty) { + public ResultsetColumnHeaderData validateToJdbcColumn(String column, @NotNull Map headersByName, + boolean allowEmpty) { final List 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 headersByName, @NotNull List errors, boolean allowEmpty) { + private ResultsetColumnHeaderData validateToJdbcColumnImpl(String column, @NotNull Map headersByName, + @NotNull List 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 columnFilters, @NotNull StringBuilder where, - @NotNull List params, String alias, Map headersByName, String dateFormat, - String dateTimeFormat, Locale locale, boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) { + public boolean buildQueryCondition(List columnFilters, @NotNull StringBuilder where, @NotNull List params, + String alias, Map 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 params, + public boolean buildFilterCondition(ColumnFilterData columnFilter, @NotNull StringBuilder where, @NotNull List params, String alias, Map 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 values, @NotNull StringBuilder where, @NotNull List params, String alias, - @NotNull DatabaseSpecificSQLGenerator sqlGenerator) { + public void buildCondition(@NotNull String definition, JdbcJavaType columnType, @NotNull SqlOperator operator, List values, + @NotNull StringBuilder where, @NotNull List 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(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java index 48b655a25..66018dd49 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java @@ -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 entries = this.journalEntryReadPlatformService.retrieveAll(params, null, null, null, null, null, null, transactionId, PortfolioProductType.PROVISIONING.getValue(), null); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java index 603f652af..dc19626f9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java @@ -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 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); diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java index 4cd899e45..3ca1ba19b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java +++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java @@ -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()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java index 8f2c34193..643809ca5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java @@ -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); diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java index f70585d0c..d7bbae6d0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/starter/AdhocQueryConfiguration.java @@ -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) { }; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java index 43486506c..9a17404a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/api/AuditsApiResource.java @@ -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 toApiJsonSerializer; private final DefaultToApiJsonSerializer 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); diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java index bfad524f3..bf4113857 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java @@ -168,7 +168,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService { @Override public Collection 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()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java index f1f388bd9..353f56a1c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookPopulatorServiceImpl.java @@ -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) this.centerReadPlatformService.retrieveAll(null, null); } else { - SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null); + SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build(); centers = (List) 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 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) this.groupReadPlatformService.retrieveAll(null, null); } else { - SearchParameters searchParameters = SearchParameters.from(officeId, null, null, null); + SearchParameters searchParameters = SearchParameters.builder().officeId(officeId).build(); groups = (List) 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; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java index 15ca3df83..d9c9690ba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java @@ -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 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 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 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 emailMessages = readPlatformService.retrieveAllFailed(searchParameters); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java index eac469e91..1faf38a0c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java @@ -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 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 smsCampaignDataCollection = smsCampaignReadPlatformService.retrieveAll(searchParameters); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializer.serialize(settings, smsCampaignDataCollection); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java index b802b19b2..48bb96846 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/EntityDatatableChecksApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/EntityDatatableChecksApiResource.java index 6a8601f2a..0a54f3eb8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/EntityDatatableChecksApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/EntityDatatableChecksApiResource.java @@ -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 result = this.readEntityDatatableChecksService.retrieveAll(searchParameters, status, entity, productId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java index 33a83f0b1..c1de71c19 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java @@ -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 exportServices; + public DatatableReportingProcessService(List exportServices, SqlValidator sqlValidator) { + super(sqlValidator); + + this.exportServices = exportServices; + } + @Override public Response processRequest(String reportName, MultivaluedMap queryParams) { boolean isSelfServiceUserReport = Boolean.parseBoolean( diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksReadPlatformServiceImpl.java index 37e604931..495713c00 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index 5cf2275a5..dea901353 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -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 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 queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValueString, @NotNull String resultColumnsString) { datatable = validateDatatableRegistered(datatable); - Map headersByName = SearchUtil + Map headersByName = searchUtil .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable)); List resultColumns = asList(resultColumnsString.split(",")); - List selectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false); - ResultsetColumnHeaderData column = SearchUtil.validateToJdbcColumn(columnName, headersByName, false); + List 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 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 headersByName = SearchUtil + Map 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 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 resultColumns = request.getNonNullResultColumns(); List 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 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 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 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 headersByName = SearchUtil + Map headersByName = searchUtil .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable)); - List thisSelectColumns = SearchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true); + List 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 columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName); - final Map mapColumnNameDefinition = SearchUtil.mapHeadersToName(columnHeaderData); + final Map 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 columnHeaders = genericDataService.fillResultsetColumnHeaders(dataTableName); - Map headersByName = SearchUtil.mapHeadersToName(columnHeaders); + Map headersByName = searchUtil.mapHeadersToName(columnHeaders); final Type typeOfMap = new TypeToken>() {}.getType(); final Map 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 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 headersByName = SearchUtil.mapHeadersToName(columnHeaders); + Map headersByName = searchUtil.mapHeadersToName(columnHeaders); final List existingValues = existingRows.getData().get(0).getRow(); HashMap 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 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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java index c0464e9d5..8425fa997 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java @@ -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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java index e13736ecf..efb9b06d0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java @@ -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 jobhistoryDetailData = this.schedulerJobRunnerReadService.retrieveJobHistory(jobId, searchParameters); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java index 291ccb56a..70ef390fe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/AbstractReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/AbstractReportingProcessService.java new file mode 100644 index 000000000..4e9bbd859 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/AbstractReportingProcessService.java @@ -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 getReportParams(final MultivaluedMap queryParams) { + final Map reportParams = new HashMap<>(); + for (Map.Entry> 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; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java index 314063278..ad3d5fafa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java @@ -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 getAvailableExportTargets(); - default Map getReportParams(final MultivaluedMap queryParams) { - final Map reportParams = new HashMap<>(); - for (Map.Entry> 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 getReportParams(MultivaluedMap queryParams); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java index 8dca897c7..3f183e2b1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java @@ -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 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 = this.reportMailingJobReadPlatformService .retrieveAllReportMailingJobs(searchParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java index 9d87238c6..37262affd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java @@ -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 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 = this.reportMailingJobRunHistoryReadPlatformService .retrieveRunHistoryByJobId(reportMailingJobId, searchParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java index 0a422be64..d6dc95e51 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java index 72bc93fd3..81b2194ab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java index e0242f269..2c20a57d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java @@ -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 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); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java index 82794bd60..9a3af759b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java index 737a8f826..ab4c89f62 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java @@ -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 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 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")) diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java index 94cc6b734..7317e572b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/api/NotificationApiResource.java @@ -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 toApiJsonSerializer; + private final SqlValidator sqlValidator; @GET @Consumes({ MediaType.APPLICATION_JSON }) @@ -71,7 +73,10 @@ public class NotificationApiResource { this.context.authenticatedUser(); final Page 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 { diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java index 1ddd1129f..67e945fc8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java index 5dcdca405..5aa464ab6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java @@ -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 offices = readPlatformService.retrieveAllOffices(onlyManualEntries, searchParameters); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializer.serialize(settings, offices, RESPONSE_DATA_PARAMETERS); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java index 3b8b4914e..d7b3a735b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java @@ -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"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java index 4effdf981..f9b9b5b5d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/teller/service/TellerManagementReadPlatformServiceImpl.java @@ -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()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java index cc0baa4f6..fd17748c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java @@ -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 transfers = this.accountTransfersReadPlatformService.retrieveAll(searchParameters, accountDetailId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java index a0a3faeb8..560b2537d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java @@ -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 associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); Page transfers = null; if (!associationParameters.isEmpty()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java index c144ec0ea..6a54364e8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionHistoryApiResource.java @@ -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 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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java index a0fb3b9d0..619da2981 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java index f3efb4e35..b663de054 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java index 3d74d6749..751e1c406 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java index 440540f0b..7948744fd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientChargesApiResource.java @@ -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 clientCharges = this.clientChargeReadPlatformService.retrieveClientCharges(clientId, chargeStatus, pendingPayment, searchParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java index 27584344b..b1a79dd90 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java @@ -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 clientTransactions = clientTransactionReadPlatformService.retrieveAllTransactions(clientId, searchParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java index 427ef7e86..a16416f6e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java @@ -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 = clientReadPlatformService.retrieveAll(searchParameters); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java index 4b03043b9..6b560a0cc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientChargeReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java index 292a21abe..13906e922 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -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) "; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java index 1c82138c1..ec42c7630 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTransactionReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java index 35c273474..7e4ff0ac3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/CentersApiResource.java @@ -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 centers = this.centerReadPlatformService.retrievePagedAll(searchParameters, parameters); return this.toApiJsonSerializer.serialize(settings, centers, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java index ae6abc139..82608b0f9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/api/GroupsApiResource.java @@ -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 groups = groupReadPlatformService.retrievePagedAll(searchParameters, parameters); return toApiJsonSerializer.serialize(settings, groups, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java index e1e9b1dc8..d6fe072d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java index 204dda969..f1bde4875 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java @@ -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()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 006149d7d..684f458a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -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 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(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 02f6cd71b..5f7efd020 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java index b88b973a2..72128a994 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java @@ -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 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 transfers = this.depositAccountOnHoldTransactionReadPlatformService .retriveAll(savingsId, guarantorFundingId, searchParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java index 3c1479555..786baf95c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java @@ -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()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java index 549f68caf..25294dcb0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java @@ -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()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java index 8448fde62..32d6a50ae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java @@ -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 products = savingsAccountReadPlatformService.retrieveAll(searchParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java index 77eada460..b19f00d99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountOnHoldTransactionReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 4a983e313..f40ca4251 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java index 1bc81f643..6ccea902e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/search/SavingsAccountTransactionsSearchServiceImpl.java @@ -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 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 headersByName = SearchUtil + Map headersByName = searchUtil .mapHeadersToName(genericDataService.fillResultsetColumnHeaders(apptable)); PageRequest pageable = searchParameters.getPageable(); @@ -84,7 +85,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou if (pageable.getSort().isSorted()) { List 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 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 columnHeaders = genericDataService.fillResultsetColumnHeaders(apptable); - Map headersByName = SearchUtil.mapHeadersToName(columnHeaders); - String pkColumn = SearchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName(); + Map headersByName = searchUtil.mapHeadersToName(columnHeaders); + String pkColumn = searchUtil.getFiltered(columnHeaders, ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName(); AdvancedQueryData baseQuery = queryRequest.getBaseQuery(); List 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 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 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); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java index 26b84a708..1d04addb6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java @@ -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 diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java index 25a36c17f..8e485b9f4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountDividendReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/api/ShareDividendApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/api/ShareDividendApiResource.java index 535baa1fb..9e04f493c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/api/ShareDividendApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/api/ShareDividendApiResource.java @@ -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 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 dividendDetails = this.shareAccountDividendReadPlatformService.retriveAll(dividendId, searchParameters); return this.toApiAccountDetailJsonSerializer.serialize(dividendDetails); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java index 2e8bc0b6c..f9f92120d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendReadPlatformServiceImpl.java @@ -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())); diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 9b31b39d5..326c3f54b 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -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:-}] diff --git a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java index 68330dd38..8d4803531 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java +++ b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java @@ -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); + } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java index b8bd9476c..018626b3f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java @@ -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 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 provideParameters() { return Stream.of(Arguments.of("timestamp without time zone", "validation.msg.invalid.dateFormat.format"), Arguments.of("INTEGER", "validation.msg.invalid.integer.format"), diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java deleted file mode 100644 index cda6583ed..000000000 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java +++ /dev/null @@ -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)); - } -} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SqlValidatorStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SqlValidatorStepDefinitions.java new file mode 100644 index 000000000..fb0bcf265 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SqlValidatorStepDefinitions.java @@ -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()); + } + }); + } +} diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 416ffc1db..fe02f28cf 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -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 diff --git a/fineract-provider/src/test/resources/features/infrastructure/infrastructure.security.utils.sqlvalidator.feature b/fineract-provider/src/test/resources/features/infrastructure/infrastructure.security.utils.sqlvalidator.feature new file mode 100644 index 000000000..8ac1d1692 --- /dev/null +++ b/fineract-provider/src/test/resources/features/infrastructure/infrastructure.security.utils.sqlvalidator.feature @@ -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 with whitespaces fuzzy degree + When Validating the partial statement + Then The validator had exception message + + 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) | +