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 bf7c1527fa6..ccf3f0d99f4 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 String retrieveProviioningEntries(@QueryParam("entryId") final Long entry @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 39818d37c63..7873e441f31 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 Page retrieveProvisioningEntries(Search 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 bd42683dd12..283a9a0a35d 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.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 String getTransactionsForCashier(@PathParam("tellerId") @Parameter(descri 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 String getTransactionsWtihSummaryForCashier(@PathParam("tellerId") @Param 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 39c7933229f..7667fdfba50 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 CashierTransactionDataValidator(final TellerManagementReadPlatformService } 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 95347b767e8..f78119b94ec 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 static class FineractModulesProperties { 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 4a2eb6bb7c8..aba4790538a 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 { - - private final boolean paged; - private final Integer offset; - private final Integer limit; - private final String orderBy; - private final String sortOrder; - - public static PaginationParameters instance(Boolean paged, Integer offset, Integer limit, String orderBy, String sortOrder) { - if (null == paged) { - paged = false; - } - - final Integer maxLimitAllowed = getCheckedLimit(limit); +@Builder +@Getter +public class PaginationParameters { - 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) { + // TODO: why do we really need this class? SearchParameters seems to provide similar functionality - final Integer maxLimitAllowed = 200; - // default to max limit first off - Integer checkedLimit = maxLimitAllowed; + public static final int DEFAULT_MAX_LIMIT = 200; - 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; + } - public String getOrderBy() { - return this.orderBy; - } + if (limit > 0) { + return limit; + } - public String getSortOrder() { - return this.sortOrder; + return null; // unlimited (0 or less) } - 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 String orderBySql() { 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 String limitSql() { 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 856c49be186..f8a17b57fc8 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 void validateParameterValues(PaginationParameters parameters, final Set 0) { + return limit; + } - 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; + return null; // unlimited (0 or less) } - 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 Boolean getOrphansOnly() { + return Boolean.TRUE.equals(orphansOnly); } - 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 getIsSelfUser() { + return Boolean.TRUE.equals(isSelfUser); } - 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 00000000000..e6c8c787d01 --- /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 00000000000..113f7d2831c --- /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 cf5c2ee86a0..f16ecd68953 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.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 @@ private Set getTableColumns(final ResultSet rs) { 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 void validateSqlInjection(String schema, String... conditions) { 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 00000000000..311473bf8d2 --- /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 999f752586f..283e53bef8c 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 @@ * 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 3170bf8d81f..36ff0d7fbb0 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 @@ 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 74c540b38e6..00000000000 --- 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 643e8f57509..b52fedfb6ff 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.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 AdHocQuerySearchConditions retrieveSearchConditions(String json) { 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 AdHocQuerySearchConditions retrieveSearchConditions(String json) { 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 AdHocQuerySearchConditions retrieveSearchConditions(String json) { 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 d8dcbba8eb6..5a2d9c5ada1 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.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.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 static ResultsetColumnHeaderData getFiltered(@NotNull Collection 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 static void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull 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 static List validateToJdbcColumns(List 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 static ResultsetColumnHeaderData validateToJdbcColumn(String column, 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 @@ private static ResultsetColumnHeaderData validateToJdbcColumnImpl(String column, 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 static boolean buildQueryCondition(List columnFilters, 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 static boolean buildFilterCondition(ColumnFilterData columnFilter, @NotNu 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 static void buildCondition(@NotNull String definition, JdbcJavaType colum } } - 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 static Object parseColumnValue(@NotNull ResultsetColumnHeaderData columnH return columnValue; } if (strict) { - SQLInjectionValidator.validateDynamicQuery(columnValue); + sqlValidator.validate(columnValue); } if (columnHeader.hasColumnValues()) { @@ -314,7 +319,7 @@ public static Object parseColumnValue(@NotNull ResultsetColumnHeaderData columnH 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 48b655a251c..66018dd498b 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 String retrieveJournalEntries(@QueryParam("offset") final Integer offset, @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 603f652afaf..dc19626f975 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 Page retrieveAll(final SearchParameters searchParameter 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 Page retrieveAll(final SearchParameters searchParameter 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 Page retrieveAll(final SearchParameters searchParameter } } - 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 Page retrieveAll(final SearchParameters searchParameter 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 Page retrieveAll(final SearchParameters searchParameter 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 @@ private String retrieveContraAccountTransactionId(final Long officeId, final Lon 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 @@ private Page retrieveContraTransactions(final Long officeId, f 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 4cd899e45c9..3ca1ba19b54 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.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 8f2c3419394..643809ca594 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 @@ 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.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 CommandProcessingResult createAdHocQuery(final JsonCommand command) { 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 f70585d0c54..d7bbae6d030 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.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 AdHocReadPlatformService adHocReadPlatformService(JdbcTemplate jdbcTempla @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 43486506c40..9a17404a204 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.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 String retrieveAuditEntries(@Context final UriInfo uriInfo, @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 bfad524f3d5..bf411385795 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 AuditData mapRow(final ResultSet rs, @SuppressWarnings("unused") final in @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 Page retrievePaginatedAuditEntries(final SQLBuilder extraCrite 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 f1f388bd9d1..353f56a1c77 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 @@ private List fetchOffices(final Long officeId) { 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 @@ private List fetchCenters(Long officeId) { 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 @@ private List fetchClients(Long officeId) { } } } 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 @@ private List fetchGroups(Long officeId) { 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 @@ private List fetchLoanAccounts(final Long officeId) { 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 15ca3df83b7..d9c9690ba6c 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.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 String retrievePendingEmail(@QueryParam("offset") final Integer offset, @ @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 String retrieveSentEmail(@QueryParam("offset") final Integer offset, @Que 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 String retrieveFailedEmail(@QueryParam("sqlSearch") final String sqlSearc 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 eac469e9195..1faf38a0c72 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.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 String retrieveCampaign(@PathParam("resourceId") final Long resourceId, @ 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 b802b19b2c2..48bb96846e1 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 Page retrieveAll(final SearchParameters searchParameters 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 6a8601f2a8c..0a54f3eb8ae 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 String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("status") @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 33a83f0b18d..c1de71c19dc 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.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.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 37e604931af..495713c00e1 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 Page retrieveAll(SearchParameters searchParame 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 5cf2275a5c2..dea90135323 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.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 List retrieveDatatableNames(final String appTable) { @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 DatatableData retrieveDatatable(final String datatable) { 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 Page queryDataTableAdvanced(@NotNull String datatable, @NotNu 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 Page queryDataTableAdvanced(@NotNull String datatable, @NotNu 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 Page queryDataTableAdvanced(@NotNull String datatable, @NotNu 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 Page queryDataTableAdvanced(@NotNull String datatable, @NotNu 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 boolean buildDataQueryEmbedded(@NotNull EntityTables entityTable, @NotNul 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 void updateDatatable(final String datatableName, final JsonCommand comman 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 @@ private CommandProcessingResult createNewDatatableEntry(final String dataTableNa 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 @@ private CommandProcessingResult createNewDatatableEntry(final String dataTableNa 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 @@ private CommandProcessingResult createNewDatatableEntry(final String dataTableNa } 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 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, 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 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, 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 @@ private CommandProcessingResult updateDatatableEntry(final String dataTableName, } 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 @@ private GenericResultsetData retrieveDataTableGenericResultSet(final EntityTable 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 @@ private String getOfficeJoinCondition(String officeHierarchy, String joinTableAl @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 @@ private void validateDatatableName(final String name) { } 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 @@ private static boolean isTechnicalParam(String param) { } 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 c0464e9d5b4..8425fa997f2 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.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 ReadWriteNonCoreDataService readWriteNonCoreDataService(final JdbcTemplat 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 e13736ecfd0..efb9b06d0fc 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.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 String retrieveHistory(@Context final UriInfo uriInfo, @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 291ccb56aee..70ef390fe4b 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 Page retrieveJobHistory(final Long jobId, final Sea 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 00000000000..4e9bbd85932 --- /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 31406327860..ad3d5fafac2 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 @@ 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 8dca897c757..3f183e2b16e 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.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 String retrieveAllReportMailingJobs(@Context final UriInfo uriInfo, .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 9d87238c64a..37262affd00 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.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 String retrieveAllByReportMailingJobId(@Context final UriInfo uriInfo, @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 0a422be64be..d6dc95e519c 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 Page retrieveAllReportMailingJobs(final SearchParam 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 Page retrieveAllReportMailingJobs(final SearchParam 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 72bc93fd35e..81b2194ab82 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 Page retrieveRunHistoryByJobId(final Long 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 e0242f269ee..2c20a57d5c2 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.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 String retrieveAllSmsByStatus(@PathParam("campaignId") final Long campaig @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 82794bd607c..9a3af759b12 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 Page retrieveSmsByStatus(final Long campaignId, final SearchPara 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 Page retrieveSmsByStatus(final Long campaignId, final SearchPara 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 737a8f826fe..ab4c89f62f0 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 @@ 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.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 @@ private String retrieveAllSurveySQL(String andClause) { @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 List retrieveClientSurveyScoreOverview(Long clientI + " 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 94cc6b73403..7317e572b75 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.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 String getAllNotifications(@Context final UriInfo uriInfo, 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 1ddd1129f0e..67e945fc833 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 @@ private Page getNotificationDataPage(SearchParameters searchPa 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 5dcdca40540..5aa464ab645 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.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 String retrieveOffices(@Context final UriInfo uriInfo, @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 3b8b4914e4e..d7b3a735b7f 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 Collection retrieveAllOffices(final boolean includeAllOffices 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 4effdf9817b..f9b9b5b5dea 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 Page retrieveCashierTransactions(final Long cashi + " 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 cc0baa4f6da..fd17748c726 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 a0a3faeb81e..560b2537d8f 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 String retrieveOne( 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 c144ec0eabc..6a54364e81b 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 a0fb3b9d002..619da29815b 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 Page retrieveAll(final SearchParameters searchParame 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 Page retrieveByStandingInstruction(final Long id, fi 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 f3efb4e3504..b663de054e5 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 Page retrieveAll(StandingInstructionDTO s } 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 3d74d6749d3..751e1c406e5 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 Page retrieveAll(final StandingInstructionDTO st } 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 440540f0b9d..7948744fdf5 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 String retrieveAllClientCharges(@PathParam("clientId") @Parameter(descrip 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 27584344b7f..b1a79dd9027 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 @@ private String getClientTransaction(Long clientId, Long transactionId, UriInfo u 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 427ef7e86b3..a16416f6efb 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.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 String retrieveAll(final UriInfo uriInfo, final Long officeId, final Stri 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 4b03043b943..6b560a0ccd8 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 Page retrieveClientCharges(Long clientId, String status // 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 292a21abe31..13906e92260 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 Page retrieveAll(final SearchParameters searchParameters) { 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 Page retrieveAll(final SearchParameters searchParameters) { 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 @@ private String buildSqlStringFromClientCriteria(String schemaSql, final SearchPa 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 1c82138c1f3..ec42c7630b2 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 Page retrieveAllTransactions(Long clientId, Search // 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 35c273474ec..7e4ff0ac38b 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 ae6abc139fd..82608b0f965 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.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 String retrieveAll(@Context final UriInfo uriInfo, @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 e1e9b1dc80b..d6fe072d5d5 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 Page retrievePagedAll(final SearchParameters searchParameters 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 Collection retrieveAll(SearchParameters searchParameters, Pag 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 204dda9698b..f1bde48751a 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 Page retrievePagedAll(final SearchParameters searchPara 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 Collection retrieveAll(SearchParameters searchParameter 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 006149d7d41..684f458a6f1 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 02f6cd71bf7..5f7efd0209e 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 Page retrieveAll(final SearchParameters searchParameters 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 b88b973a29a..72128a994d6 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.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 String retrieveAll(@PathParam("savingsId") final Long savingsId, @QueryPa 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 3c147955508..786baf95cf5 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.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 String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @ @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 549f68caf27..25294dcb023 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.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 String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @ @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 8448fde62f6..32d6a50ae54 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.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 String retrieveAll(@Context final UriInfo uriInfo, 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 77eada460b8..b19f00d99df 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 Page retriveAll(Long savingsId, Long 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 4a983e31328..f40ca425140 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 Page retrieveAll(final SearchParameters searchParamet 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 1bc81f6438a..6ccea902e4e 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 Page searchTransactions(@NotNull Long savi 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 Page searchTransactions(@NotNull Long savi 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 Page searchTransactions(@NotNull Long savi 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 Page queryAdvanced(@NotNull Long savingsId, @NotNull PagedLoc 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 Page queryAdvanced(@NotNull Long savingsId, @NotNull PagedLoc 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 Page queryAdvanced(@NotNull Long savingsId, @NotNull PagedLoc 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 Page queryAdvanced(@NotNull Long savingsId, @NotNull PagedLoc 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 Page queryAdvanced(@NotNull Long savingsId, @NotNull PagedLoc 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 26b84a7088d..1d04addb61a 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.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.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 25a36c17f79..8e485b9f41d 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 Page retriveAll(final Long payoutDetailId, fina 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 535baa1fb3b..9e04f493c14 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.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 String retrieveAll(@PathParam("productId") final Long productId, @QueryPa @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 String retrieveDividendDetails(@PathParam("dividendId") final Long divide @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 2e8bc0b6cd9..f9f92120d3b 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 Page retriveAll(final Long productId, fin 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 9b31b39d55f..326c3f54b3f 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 68330dd38f4..8d48035316d 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.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.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 DataSource create(FineractPlatformTenant tenant) { }; } - @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 JdbcTemplate jdbcTemplate() { @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 JobOperator jobOperator() { 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 b8bd9476c0f..018626b3fd5 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 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 void testSqlInjectionCaughtQueryDataTable() { @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 void testSqlInjectionCaughtQueryDataTable2() { @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 void testQueryDataTableSuccess() { 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 void testQueryDataTableSuccess() { @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 void testDatatableValidationError() { @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 void testQueryDataTableInvalidParameterError(String columnType, String er 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 cda6583edbb..00000000000 --- 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 00000000000..fb0bcf26595 --- /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 416ffc1dbcd..fe02f28cf8c 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 00000000000..8ac1d1692c1 --- /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) | +