[FINERACT-1678] Bypass LoanAccount Write Protection

This commit is contained in:
taskain7
2022-11-02 11:57:31 +01:00
committed by Arnold Gálovics
parent ee3019603c
commit cce7902054
6 changed files with 85 additions and 4 deletions
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter;
import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository;
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
import org.apache.fineract.infrastructure.jobs.service.InlineExecutorService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants;
import org.jetbrains.annotations.NotNull;
import org.springframework.batch.core.BatchStatus;
@@ -77,6 +78,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService,
private final JobExplorer jobExplorer;
private final TransactionTemplate transactionTemplate;
private final CustomJobParameterRepository customJobParameterRepository;
private final PlatformSecurityContext context;
private Gson gson;
@@ -168,10 +170,14 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService,
}
private boolean isLockOverrulable(LoanAccountLock loanAccountLock) {
if (LockOwner.LOAN_COB_PARTITIONING.equals(loanAccountLock.getLockOwner())) {
if (LockOwner.LOAN_COB_PARTITIONING.equals(loanAccountLock.getLockOwner()) || isBypassUser()) {
return true;
} else {
return StringUtils.isNotBlank(loanAccountLock.getError());
}
}
private boolean isBypassUser() {
return context.getAuthenticatedUserIfPresent().isBypassUser();
}
}
@@ -35,6 +35,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -49,6 +50,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter {
private final GLIMAccountInfoRepository glimAccountInfoRepository;
private final LoanAccountLockService loanAccountLockService;
private final PlatformSecurityContext context;
private static final List<HttpMethod> HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE);
private static final Function<String, Boolean> URL_FUNCTION = s -> s.matches("/loans/\\d+.*") || s.matches("/loans/glimAccount/\\d+.*");
@@ -59,7 +61,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!isOnApiList(request)) {
if (!isOnApiList(request) || isBypassUser()) {
proceed(filterChain, request, response);
} else {
Iterable<String> split = Splitter.on('/').split(request.getPathInfo());
@@ -74,6 +76,10 @@ public class LoanCOBApiFilter extends OncePerRequestFilter {
}
}
private boolean isBypassUser() {
return context.getAuthenticatedUserIfPresent().isBypassUser();
}
private boolean isLoanLocked(Long loanId, boolean isGlim) {
if (!isGlim) {
return loanAccountLockService.isLoanHardLocked(loanId);
@@ -434,6 +434,10 @@ public class AppUser extends AbstractPersistableCustom implements PlatformUser {
return this.enabled;
}
public boolean isBypassUser() {
return hasAnyPermission("BYPASS_LOAN_WRITE_PROTECTION");
}
public String getFirstname() {
return this.firstname;
}
@@ -84,4 +84,5 @@
<include file="parts/0062_add_fraud_attribute_to_loan.xml" relativeToChangelogFile="true"/>
<include file="parts/0063_add_permissions_for_external_event_configuration.xml" relativeToChangelogFile="true"/>
<include file="parts/0064_refactor_loan_transaction_strategy.xml" relativeToChangelogFile="true"/>
<include file="parts/0065_add_bypass_loan_write_transaction_permission.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<insert tableName="m_permission">
<column name="grouping" value="transaction_loan"/>
<column name="code" value="BYPASS_LOAN_WRITE_PROTECTION"/>
<column name="entity_name" value="LOAN"/>
<column name="action_name" value="BYPASS"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -31,9 +31,11 @@ import java.util.Collections;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -55,14 +57,34 @@ class LoanCOBApiFilterTest {
private LoanAccountLockService loanAccountLockService;
@Mock
private GLIMAccountInfoRepository glimAccountInfoRepository;
@Mock
private PlatformSecurityContext context;
@Test
void shouldProceedWhenUrlDoesNotMatch() throws ServletException, IOException {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
given(request.getPathInfo()).willReturn("/jobs/2/inline");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
given(request.getPathInfo()).willReturn("/jobs/2/inline");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
testObj.doFilterInternal(request, response, filterChain);
verify(filterChain, times(1)).doFilter(request, response);
}
@Test
void shouldProceedWhenUserHasBypassPermission() throws ServletException, IOException {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
AppUser appUser = mock(AppUser.class);
given(request.getPathInfo()).willReturn("/jobs/2/inline");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
given(appUser.isBypassUser()).willReturn(true);
testObj.doFilterInternal(request, response, filterChain);
verify(filterChain, times(1)).doFilter(request, response);
}
@@ -72,10 +94,12 @@ class LoanCOBApiFilterTest {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
AppUser appUser = mock(AppUser.class);
given(request.getPathInfo()).willReturn("/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
verify(filterChain, times(1)).doFilter(request, response);
@@ -86,10 +110,12 @@ class LoanCOBApiFilterTest {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
AppUser appUser = mock(AppUser.class);
given(request.getPathInfo()).willReturn("/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
verify(filterChain, times(1)).doFilter(request, response);
@@ -101,11 +127,13 @@ class LoanCOBApiFilterTest {
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
PrintWriter writer = mock(PrintWriter.class);
AppUser appUser = mock(AppUser.class);
given(request.getPathInfo()).willReturn("/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(true);
given(response.getWriter()).willReturn(writer);
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
verify(response, times(1)).setStatus(HttpStatus.SC_CONFLICT);
@@ -120,6 +148,7 @@ class LoanCOBApiFilterTest {
GroupLoanIndividualMonitoringAccount glimAccount = mock(GroupLoanIndividualMonitoringAccount.class);
Loan loan = mock(Loan.class);
Long loanId = 2L;
AppUser appUser = mock(AppUser.class);
given(request.getPathInfo()).willReturn("/loans/glimAccount/2");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
@@ -128,6 +157,7 @@ class LoanCOBApiFilterTest {
given(loan.getId()).willReturn(loanId);
given(loanAccountLockService.isLoanHardLocked(loanId)).willReturn(true);
given(response.getWriter()).willReturn(writer);
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
verify(response, times(1)).setStatus(HttpStatus.SC_CONFLICT);