From 0aa2cf329d9ae1e68412751cebb6892da55ebcb9 Mon Sep 17 00:00:00 2001 From: Emanuel Castillo Date: Fri, 12 Jun 2026 20:37:37 -0600 Subject: [PATCH] FINERACT-2639: Implement office-hierarchy access checks for monetary transactions --- ...ountTransfersWritePlatformServiceImpl.java | 21 ++++++++++++ .../account/starter/AccountConfiguration.java | 6 ++-- ...WritePlatformServiceJpaRepositoryImpl.java | 34 ++++++++++++++++++- ...SavingsAccountReadPlatformServiceImpl.java | 8 +++-- ...WritePlatformServiceJpaRepositoryImpl.java | 30 ++++++++++++++-- 5 files changed, 91 insertions(+), 8 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index 8fce2c4ecec..2a593d80745 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -43,6 +43,7 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator; @@ -93,6 +94,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer private final ExternalIdFactory externalIdFactory; private final FineractProperties fineractProperties; private final LoanAdjustmentService loanAdjustmentService; + private final PlatformSecurityContext context; @Transactional @Override @@ -127,6 +129,7 @@ public CommandProcessingResult create(final JsonCommand command) { fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(fromSavingsAccount.office().getHierarchy()); final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance); @@ -135,6 +138,7 @@ public CommandProcessingResult create(final JsonCommand command) { final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(toSavingsAccount.office().getHierarchy()); final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill); @@ -154,6 +158,7 @@ public CommandProcessingResult create(final JsonCommand command) { fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(fromSavingsAccount.office().getHierarchy()); final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance); @@ -162,6 +167,7 @@ public CommandProcessingResult create(final JsonCommand command) { final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName); Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId); + this.context.validateAccessRights(toLoanAccount.getOffice().getHierarchy()); final Boolean isHolidayValidationDone = false; final HolidayDetailDTO holidayDetailDto = null; @@ -184,12 +190,14 @@ public CommandProcessingResult create(final JsonCommand command) { fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId); + this.context.validateAccessRights(fromLoanAccount.getOffice().getHierarchy()); ExternalId externalId = externalIdFactory.create(); final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefund(fromLoanAccountId, new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, externalId); final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(toSavingsAccount.office().getHierarchy()); final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill); @@ -307,6 +315,8 @@ public Long transferFunds(final AccountTransferDTO accountTransferDTO) { this.savingsAccountAssembler.setHelpers(fromSavingsAccount); toLoanAccount = accountTransferDetails.toLoanAccount(); } + this.context.validateAccessRights(fromSavingsAccount.office().getHierarchy()); + this.context.validateAccessRights(toLoanAccount.getOffice().getHierarchy()); final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), @@ -379,6 +389,8 @@ public Long transferFunds(final AccountTransferDTO accountTransferDTO) { toSavingsAccount = accountTransferDetails.toSavingsAccount(); this.savingsAccountAssembler.setHelpers(toSavingsAccount); } + this.context.validateAccessRights(fromSavingsAccount.office().getHierarchy()); + this.context.validateAccessRights(toSavingsAccount.office().getHierarchy()); final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), @@ -421,6 +433,9 @@ public Long transferFunds(final AccountTransferDTO accountTransferDTO) { toSavingsAccount = accountTransferDetails.toSavingsAccount(); this.savingsAccountAssembler.setHelpers(toSavingsAccount); } + this.context.validateAccessRights(fromLoanAccount.getOffice().getHierarchy()); + this.context.validateAccessRights(toSavingsAccount.office().getHierarchy()); + LoanTransaction loanTransaction = null; ExternalId txnExternalId = accountTransferDTO.getTxnExternalId(); @@ -471,12 +486,16 @@ public AccountTransferDetails repayLoanWithTopup(AccountTransferDTO accountTrans } else { fromLoanAccount = accountTransferDTO.getFromLoan(); } + this.context.validateAccessRights(fromLoanAccount.getOffice().getHierarchy()); + Loan toLoanAccount = null; if (accountTransferDTO.getToLoan() == null) { toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); } else { toLoanAccount = accountTransferDTO.getToLoan(); } + this.context.validateAccessRights(fromLoanAccount.getOffice().getHierarchy()); + this.context.validateAccessRights(toLoanAccount.getOffice().getHierarchy()); ExternalId externalIdForDisbursement = accountTransferDTO.getTxnExternalId(); @@ -581,6 +600,7 @@ public CommandProcessingResult refundByTransfer(JsonCommand command) { final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId); + this.context.validateAccessRights(fromLoanAccount.getOffice().getHierarchy()); BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance(); final boolean backdatedTxnsAllowedTill = false; @@ -599,6 +619,7 @@ public CommandProcessingResult refundByTransfer(JsonCommand command) { final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(toSavingsAccount.office().getHierarchy()); final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate, transactionAmount, paymentDetail, true, true, backdatedTxnsAllowedTill); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java index c538968909d..f8908e8a979 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java @@ -23,6 +23,7 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.PaginationHelper; 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.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; @@ -92,11 +93,12 @@ public AccountTransfersWritePlatformService accountTransfersWritePlatformService LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService, AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService, GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory, - FineractProperties fineractProperties, LoanAdjustmentService loanAdjustmentService) { + FineractProperties fineractProperties, LoanAdjustmentService loanAdjustmentService, PlatformSecurityContext context) { + return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler, accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler, loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService, - gsimRepository, configurationDomainService, externalIdFactory, fineractProperties, loanAdjustmentService); + gsimRepository, configurationDomainService, externalIdFactory, fineractProperties, loanAdjustmentService, context); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 8c40701ebee..10fb309ffff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -300,6 +300,7 @@ public CommandProcessingResult disburseGLIMLoan(final Long loanId, final JsonCom CommandProcessingResult result = null; int count = 0; for (Loan loan : childLoans) { + this.context.validateAccessRights(loan.getOffice().getHierarchy()); result = disburseLoan(loan.getId(), command, false); if (result.getLoanId() != null) { count++; @@ -326,6 +327,7 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand loanTransactionValidator.validateDisbursement(command, isAccountTransfer, loanId); Loan loan = loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); if (loan.loanProduct().isDisallowExpectedDisbursements()) { List filteredList = loan.getDisbursementDetails().stream() @@ -750,6 +752,7 @@ public Map bulkLoanDisbursal(final JsonCommand command, final Co for (final SingleDisbursalCommand singleLoanDisbursalCommand : disbursalCommand) { Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId()); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); // validate ActualDisbursement Date Against Expected Disbursement @@ -859,6 +862,7 @@ public CommandProcessingResult undoGLIMLoanDisbursal(final Long loanId, final Js CommandProcessingResult result = null; int count = 0; for (Loan loan : childLoans) { + this.context.validateAccessRights(loan.getOffice().getHierarchy()); result = undoLoanDisbursal(loan.getId(), command); if (result.getLoanId() != null) { count++; @@ -878,6 +882,7 @@ public CommandProcessingResult undoGLIMLoanDisbursal(final Long loanId, final Js public CommandProcessingResult undoLoanDisbursal(final Long loanId, final JsonCommand command) { Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); checkClientOrGroupActive(loan); if (loan.isChargedOff()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off", @@ -1040,7 +1045,7 @@ public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand comma final Long loanId = command.getLoanId(); Loan loan = this.loanAssembler.assembleFrom(loanId); - + this.context.validateAccessRights(loan.getOffice().getHierarchy()); businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestPaymentWaiverPreBusinessEvent(loan)); final String noteText = command.stringValueOfParameterNamed("note"); @@ -1122,6 +1127,8 @@ public CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(final changes.put(LoanApiConstants.externalIdParameterName, txnExternalId); } Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final Boolean isHolidayValidationDone = false; final HolidayDetailDTO holidayDetailDto = null; @@ -1164,6 +1171,7 @@ public Map makeLoanBulkRepayment(final CollectionSheetBulkRepaym for (final SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) { if (singleLoanRepaymentCommand != null) { Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId()); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), singleLoanRepaymentCommand.getTransactionDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); @@ -1184,6 +1192,7 @@ public Map makeLoanBulkRepayment(final CollectionSheetBulkRepaym for (final SingleRepaymentCommand singleLoanRepaymentCommand : repaymentCommand) { if (singleLoanRepaymentCommand != null) { final Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId()); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final PaymentDetail paymentDetail = singleLoanRepaymentCommand.getPaymentDetail(); ExternalId externalId = singleLoanRepaymentCommand.getExternalId(); if (externalId.isEmpty() && configurationDomainService.isExternalIdAutoGenerationEnabled()) { @@ -1213,6 +1222,7 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo .orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId(), command.getLoanId())); Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); if (loan.getStatus().isClosed() && loan.getLoanSubStatus() != null && loan.getLoanSubStatus().equals(LoanSubStatus.FORECLOSED)) { final String defaultUserMessage = "The loan cannot reopened as it is foreclosed."; throw new LoanForeclosureException("loan.cannot.be.reopened.as.it.is.foreclosured", defaultUserMessage, loanId); @@ -1296,6 +1306,7 @@ public CommandProcessingResult chargebackLoanTransaction(final Long loanId, fina } Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction:" + transactionId + " chargeback not allowed as it involves in account transfer", transactionId); @@ -1403,6 +1414,7 @@ public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final Json final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName); Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); loanTransactionValidator.validateTransaction(loan, LoanTransactionType.WAIVE_INTEREST, command.json()); checkClientOrGroupActive(loan); @@ -1478,6 +1490,7 @@ public CommandProcessingResult writeOff(final Long loanId, final JsonCommand com changes.put("dateFormat", command.dateFormat()); LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); if (command.hasParameter("writeoffReasonId")) { Long writeoffReasonId = command.longValueOfParameterNamed("writeoffReasonId"); CodeValue writeoffReason = this.codeValueRepository @@ -1555,6 +1568,7 @@ public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand co this.loanTransactionValidator.validateTransactionWithNoAmount(command.json()); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); checkClientOrGroupActive(loan); LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); if (loan.isChargedOff() && DateUtils.isBefore(transactionDate, loan.getChargedOffOnDate())) { @@ -1650,6 +1664,7 @@ public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonC this.loanTransactionValidator.validateTransactionWithNoAmount(command.json()); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); checkClientOrGroupActive(loan); if (loan.isChargedOff()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.is.charged.off", @@ -1816,6 +1831,7 @@ public CommandProcessingResult loanReassignment(final Long loanId, final JsonCom final LocalDate dateOfLoanOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate"); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); checkClientOrGroupActive(loan); businessEventNotifierService.notifyPreBusinessEvent(new LoanReassignOfficerBusinessEvent(loan)); if (!loan.hasLoanOfficer(fromLoanOfficer)) { @@ -1860,6 +1876,7 @@ public CommandProcessingResult bulkLoanReassignment(final JsonCommand command) { for (final Long loanId : listLoanIds) { final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); businessEventNotifierService.notifyPreBusinessEvent(new LoanReassignOfficerBusinessEvent(loan)); checkClientOrGroupActive(loan); @@ -1888,6 +1905,7 @@ public CommandProcessingResult removeLoanOfficer(final Long loanId, final JsonCo final LocalDate dateOfLoanOfficerUnassigned = command.localDateValueOfParameterNamed("unassignedDate"); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); checkClientOrGroupActive(loan); if (loan.getLoanOfficer() == null) { @@ -1948,6 +1966,7 @@ public void applyMeetingDateChanges(final Calendar calendar, final Collection changes = new LinkedHashMap<>(); final boolean fraud = command.booleanPrimitiveValueOfParameterNamed(LoanApiConstants.FRAUD_ATTRIBUTE_NAME); if (loan.isFraud() != fraud) { @@ -2626,6 +2652,7 @@ private AppUser getAppUserIfPresent() { public CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command) { Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final LocalDate recalculateFromDate = loan.getLastRepaymentDate(); validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(loan); checkClientOrGroupActive(loan); @@ -2670,6 +2697,7 @@ public CommandProcessingResult forecloseLoan(final Long loanId, final JsonComman final String json = command.json(); final JsonElement element = fromApiJsonHelper.parse(json); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.transactionDateParamName, element); final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName); this.loanTransactionValidator.validateLoanForeclosure(command.json()); @@ -2719,6 +2747,7 @@ public CommandProcessingResult chargeOff(JsonCommand command) { final AppUser currentUser = getAppUserIfPresent(); Loan loan = loanAssembler.assembleFrom(command.getLoanId()); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final Long loanId = loan.getId(); if (!loan.isOpen()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.active", @@ -2815,6 +2844,7 @@ public CommandProcessingResult undoChargeOff(JsonCommand command) { this.loanTransactionValidator.validateUndoChargeOff(command.json()); final Long loanId = command.getLoanId(); final Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); checkClientOrGroupActive(loan); if (!loan.isOpen()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.active", @@ -2887,6 +2917,7 @@ public CommandProcessingResult makeRefund(final Long loanId, final LoanTransacti changes.put(LoanApiConstants.externalIdParameterName, txnExternalId); Loan loan = this.loanAssembler.assembleFrom(loanId); + this.context.validateAccessRights(loan.getOffice().getHierarchy()); // Build loan schedule generator dto LocalDate recalculateFrom = loan.isInterestBearingAndInterestRecalculationEnabled() ? transactionDate : null; final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null); @@ -2960,6 +2991,7 @@ public CommandProcessingResult makeManualInterestRefund(final Long loanId, final if (loan == null) { throw new LoanNotFoundException(loanId); } + this.context.validateAccessRights(loan.getOffice().getHierarchy()); final LoanTransaction targetTransaction = this.loanTransactionRepository.findByIdAndLoanId(transactionId, loanId) .orElseThrow(() -> new LoanTransactionNotFoundException(transactionId, loanId)); 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 674e8442d1c..759774de62d 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 @@ -218,10 +218,14 @@ public Page retrieveAll(final SearchParameters searchParamet @Override public SavingsAccountData retrieveOne(final Long accountId) { + final AppUser currentUser = this.context.authenticatedUser(); + final String hierarchy = currentUser.getOffice().getHierarchy(); + try { - final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?"; + final String sql = "select " + this.savingAccountMapper.schema() + " join m_office o on o.id = c.office_id" + + " where sa.id = ? and o.hierarchy like ?"; - return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId }); // NOSONAR + return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId, hierarchy + "%" }); } catch (final EmptyResultDataAccessException e) { throw new SavingsAccountNotFoundException(accountId, e); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 063107b1530..d44b7dbb6c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -180,6 +180,7 @@ public CommandProcessingResult gsimActivate(final Long gsimId, final JsonCommand CommandProcessingResult result = null; int count = 0; for (SavingsAccount account : childSavings) { + this.context.validateAccessRights(account.office().getHierarchy()); result = activate(account.getId(), command); if (result != null) { count++; @@ -201,6 +202,7 @@ public CommandProcessingResult activate(final Long savingsId, final JsonCommand this.savingsAccountTransactionDataValidator.validateActivation(command); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); final Set existingTransactionIds = new HashSet<>(); @@ -286,6 +288,7 @@ public CommandProcessingResult deposit(final Long savingsId, final JsonCommand c final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); if (account.getGsim() != null) { isGsim = true; @@ -369,6 +372,7 @@ public CommandProcessingResult withdrawal(final Long savingsId, final JsonComman final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); if (account.getGsim() != null) { isGsim = true; @@ -434,6 +438,7 @@ public CommandProcessingResult forceWithdrawal(final Long savingsId, final JsonC final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); if (account.getGsim() != null) { isGsim = true; @@ -512,6 +517,7 @@ public CommandProcessingResult calculateInterest(final Long savingsId) { final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); final LocalDate today = DateUtils.getBusinessLocalDate(); @@ -549,6 +555,7 @@ public CommandProcessingResult postInterest(final JsonCommand command) { final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account); @@ -734,7 +741,7 @@ public CommandProcessingResult reverseTransaction(final Long savingsId, final Lo final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final boolean isBulk = command.booleanPrimitiveValueOfParameterNamed("isBulk"); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); - + this.context.validateAccessRights(account.office().getHierarchy()); final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository .findOneByIdAndSavingsAccountId(transactionId, savingsId); if (savingsAccountTransaction == null) { @@ -785,6 +792,7 @@ public CommandProcessingResult undoTransaction(final Long savingsId, final Long .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -887,7 +895,7 @@ public CommandProcessingResult adjustSavingsTransaction(final Long savingsId, fi final LocalDate today = DateUtils.getBusinessLocalDate(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); - + this.context.validateAccessRights(account.office().getHierarchy()); if (account.isNotActive()) { throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction); } @@ -974,6 +982,7 @@ private void throwValidationForActiveStatus(final String actionName) { } private void checkClientOrGroupActive(final SavingsAccount account) { + this.context.validateAccessRights(account.office().getHierarchy()); final Client client = account.getClient(); if (client != null) { if (client.isNotActive()) { @@ -998,6 +1007,7 @@ public CommandProcessingResult bulkGSIMClose(final Long gsimId, final JsonComman CommandProcessingResult result = null; int count = 0; for (SavingsAccount account : childSavings) { + this.context.validateAccessRights(account.office().getHierarchy()); result = close(account.getId(), command); if (result != null) { @@ -1016,6 +1026,7 @@ public CommandProcessingResult close(final Long savingsId, final JsonCommand com final AppUser user = this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); this.savingsAccountTransactionDataValidator.validateClosing(command, account); final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId); @@ -1342,7 +1353,7 @@ public CommandProcessingResult waiveCharge(final Long savingsAccountId, final Lo // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); - + this.context.validateAccessRights(account.office().getHierarchy()); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); this.savingAccountAssembler.loadTransactionsToSavingsAccount(account, backdatedTxnsAllowedTill); @@ -1510,6 +1521,7 @@ private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAc // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.context.validateAccessRights(account.office().getHierarchy()); this.savingAccountAssembler.assignSavingAccountHelpers(account); final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); @@ -1591,6 +1603,7 @@ public CommandProcessingResult inactivateCharge(final Long savingsAccountId, fin .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.context.validateAccessRights(account.office().getHierarchy()); this.savingAccountAssembler.assignSavingAccountHelpers(account); final LocalDate inactivationOnDate = DateUtils.getBusinessLocalDate(); @@ -1751,6 +1764,7 @@ public CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonComm @Override public void setSubStatusInactive(Long savingsId) { final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -1762,6 +1776,7 @@ public void setSubStatusInactive(Long savingsId) { @Override public void setSubStatusDormant(Long savingsId) { final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); account.setSubStatusDormant(); this.savingAccountRepositoryWrapper.saveAndFlush(account); } @@ -1769,6 +1784,7 @@ public void setSubStatusDormant(Long savingsId) { @Override public void escheat(Long savingsId) { final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -1814,6 +1830,7 @@ public CommandProcessingResult blockAccount(final Long savingsId, final JsonComm this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); final Map changes = account.block(); @@ -1841,6 +1858,7 @@ public CommandProcessingResult unblockAccount(final Long savingsId) { this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); final Map changes = account.unblock(); @@ -1868,6 +1886,7 @@ public CommandProcessingResult holdAmount(final Long savingsId, final JsonComman final AppUser submittedBy = this.context.authenticatedUser(); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); final LocalDate transactionDate = command.localDateValueOfParameterNamed(transactionDateParamName); final ExternalId externalId = this.externalIdFactory.createFromCommand(command, SavingsApiConstants.externalIdParamName); final boolean lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); @@ -1934,6 +1953,7 @@ public CommandProcessingResult releaseAmount(final Long savingsId, final Long sa final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); Money runningBalance = Money.of(account.getCurrency(), account.getAccountBalance()); @@ -1974,6 +1994,7 @@ public CommandProcessingResult blockCredits(final Long savingsId, final JsonComm this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); @@ -2001,6 +2022,7 @@ public CommandProcessingResult unblockCredits(final Long savingsId) { this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); account.updateReason(null); final Map changes = account.unblockCredits(); @@ -2024,6 +2046,7 @@ public CommandProcessingResult blockDebits(final Long savingsId, final JsonComma this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); @@ -2051,6 +2074,7 @@ public CommandProcessingResult unblockDebits(final Long savingsId) { this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); + this.context.validateAccessRights(account.office().getHierarchy()); checkClientOrGroupActive(account); account.updateReason(null);