[FINERACT-1678] Bypass LoanAccount Write Protection
This commit is contained in:
committed by
Arnold Gálovics
parent
ee3019603c
commit
cce7902054
+7
-1
@@ -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.domain.CustomJobParameterRepository;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
|
||||||
import org.apache.fineract.infrastructure.jobs.service.InlineExecutorService;
|
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.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.batch.core.BatchStatus;
|
import org.springframework.batch.core.BatchStatus;
|
||||||
@@ -77,6 +78,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService,
|
|||||||
private final JobExplorer jobExplorer;
|
private final JobExplorer jobExplorer;
|
||||||
private final TransactionTemplate transactionTemplate;
|
private final TransactionTemplate transactionTemplate;
|
||||||
private final CustomJobParameterRepository customJobParameterRepository;
|
private final CustomJobParameterRepository customJobParameterRepository;
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
|
|
||||||
@@ -168,10 +170,14 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLockOverrulable(LoanAccountLock loanAccountLock) {
|
private boolean isLockOverrulable(LoanAccountLock loanAccountLock) {
|
||||||
if (LockOwner.LOAN_COB_PARTITIONING.equals(loanAccountLock.getLockOwner())) {
|
if (LockOwner.LOAN_COB_PARTITIONING.equals(loanAccountLock.getLockOwner()) || isBypassUser()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return StringUtils.isNotBlank(loanAccountLock.getError());
|
return StringUtils.isNotBlank(loanAccountLock.getError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBypassUser() {
|
||||||
|
return context.getAuthenticatedUserIfPresent().isBypassUser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -35,6 +35,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.cob.service.LoanAccountLockService;
|
import org.apache.fineract.cob.service.LoanAccountLockService;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
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.GLIMAccountInfoRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
@@ -49,6 +50,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
private final GLIMAccountInfoRepository glimAccountInfoRepository;
|
private final GLIMAccountInfoRepository glimAccountInfoRepository;
|
||||||
private final LoanAccountLockService loanAccountLockService;
|
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 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+.*");
|
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
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
if (!isOnApiList(request)) {
|
if (!isOnApiList(request) || isBypassUser()) {
|
||||||
proceed(filterChain, request, response);
|
proceed(filterChain, request, response);
|
||||||
} else {
|
} else {
|
||||||
Iterable<String> split = Splitter.on('/').split(request.getPathInfo());
|
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) {
|
private boolean isLoanLocked(Long loanId, boolean isGlim) {
|
||||||
if (!isGlim) {
|
if (!isGlim) {
|
||||||
return loanAccountLockService.isLoanHardLocked(loanId);
|
return loanAccountLockService.isLoanHardLocked(loanId);
|
||||||
|
|||||||
+4
@@ -434,6 +434,10 @@ public class AppUser extends AbstractPersistableCustom implements PlatformUser {
|
|||||||
return this.enabled;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBypassUser() {
|
||||||
|
return hasAnyPermission("BYPASS_LOAN_WRITE_PROTECTION");
|
||||||
|
}
|
||||||
|
|
||||||
public String getFirstname() {
|
public String getFirstname() {
|
||||||
return this.firstname;
|
return this.firstname;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,4 +84,5 @@
|
|||||||
<include file="parts/0062_add_fraud_attribute_to_loan.xml" relativeToChangelogFile="true"/>
|
<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/0063_add_permissions_for_external_event_configuration.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0064_refactor_loan_transaction_strategy.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>
|
</databaseChangeLog>
|
||||||
|
|||||||
+34
@@ -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>
|
||||||
+32
-2
@@ -31,9 +31,11 @@ import java.util.Collections;
|
|||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import org.apache.fineract.cob.service.LoanAccountLockService;
|
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.GLIMAccountInfoRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -55,14 +57,34 @@ class LoanCOBApiFilterTest {
|
|||||||
private LoanAccountLockService loanAccountLockService;
|
private LoanAccountLockService loanAccountLockService;
|
||||||
@Mock
|
@Mock
|
||||||
private GLIMAccountInfoRepository glimAccountInfoRepository;
|
private GLIMAccountInfoRepository glimAccountInfoRepository;
|
||||||
|
@Mock
|
||||||
|
private PlatformSecurityContext context;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldProceedWhenUrlDoesNotMatch() throws ServletException, IOException {
|
void shouldProceedWhenUrlDoesNotMatch() throws ServletException, IOException {
|
||||||
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
|
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
|
||||||
given(request.getPathInfo()).willReturn("/jobs/2/inline");
|
|
||||||
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
|
||||||
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
||||||
FilterChain filterChain = mock(FilterChain.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);
|
testObj.doFilterInternal(request, response, filterChain);
|
||||||
verify(filterChain, times(1)).doFilter(request, response);
|
verify(filterChain, times(1)).doFilter(request, response);
|
||||||
}
|
}
|
||||||
@@ -72,10 +94,12 @@ class LoanCOBApiFilterTest {
|
|||||||
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
|
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
|
||||||
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
AppUser appUser = mock(AppUser.class);
|
||||||
|
|
||||||
given(request.getPathInfo()).willReturn("/loans/2/charges");
|
given(request.getPathInfo()).willReturn("/loans/2/charges");
|
||||||
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
||||||
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
|
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
|
||||||
|
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
|
||||||
|
|
||||||
testObj.doFilterInternal(request, response, filterChain);
|
testObj.doFilterInternal(request, response, filterChain);
|
||||||
verify(filterChain, times(1)).doFilter(request, response);
|
verify(filterChain, times(1)).doFilter(request, response);
|
||||||
@@ -86,10 +110,12 @@ class LoanCOBApiFilterTest {
|
|||||||
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
|
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
|
||||||
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
AppUser appUser = mock(AppUser.class);
|
||||||
|
|
||||||
given(request.getPathInfo()).willReturn("/loans/2/charges");
|
given(request.getPathInfo()).willReturn("/loans/2/charges");
|
||||||
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
||||||
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
|
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
|
||||||
|
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
|
||||||
|
|
||||||
testObj.doFilterInternal(request, response, filterChain);
|
testObj.doFilterInternal(request, response, filterChain);
|
||||||
verify(filterChain, times(1)).doFilter(request, response);
|
verify(filterChain, times(1)).doFilter(request, response);
|
||||||
@@ -101,11 +127,13 @@ class LoanCOBApiFilterTest {
|
|||||||
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
PrintWriter writer = mock(PrintWriter.class);
|
PrintWriter writer = mock(PrintWriter.class);
|
||||||
|
AppUser appUser = mock(AppUser.class);
|
||||||
|
|
||||||
given(request.getPathInfo()).willReturn("/loans/2/charges");
|
given(request.getPathInfo()).willReturn("/loans/2/charges");
|
||||||
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
||||||
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(true);
|
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(true);
|
||||||
given(response.getWriter()).willReturn(writer);
|
given(response.getWriter()).willReturn(writer);
|
||||||
|
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
|
||||||
|
|
||||||
testObj.doFilterInternal(request, response, filterChain);
|
testObj.doFilterInternal(request, response, filterChain);
|
||||||
verify(response, times(1)).setStatus(HttpStatus.SC_CONFLICT);
|
verify(response, times(1)).setStatus(HttpStatus.SC_CONFLICT);
|
||||||
@@ -120,6 +148,7 @@ class LoanCOBApiFilterTest {
|
|||||||
GroupLoanIndividualMonitoringAccount glimAccount = mock(GroupLoanIndividualMonitoringAccount.class);
|
GroupLoanIndividualMonitoringAccount glimAccount = mock(GroupLoanIndividualMonitoringAccount.class);
|
||||||
Loan loan = mock(Loan.class);
|
Loan loan = mock(Loan.class);
|
||||||
Long loanId = 2L;
|
Long loanId = 2L;
|
||||||
|
AppUser appUser = mock(AppUser.class);
|
||||||
|
|
||||||
given(request.getPathInfo()).willReturn("/loans/glimAccount/2");
|
given(request.getPathInfo()).willReturn("/loans/glimAccount/2");
|
||||||
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
|
||||||
@@ -128,6 +157,7 @@ class LoanCOBApiFilterTest {
|
|||||||
given(loan.getId()).willReturn(loanId);
|
given(loan.getId()).willReturn(loanId);
|
||||||
given(loanAccountLockService.isLoanHardLocked(loanId)).willReturn(true);
|
given(loanAccountLockService.isLoanHardLocked(loanId)).willReturn(true);
|
||||||
given(response.getWriter()).willReturn(writer);
|
given(response.getWriter()).willReturn(writer);
|
||||||
|
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
|
||||||
|
|
||||||
testObj.doFilterInternal(request, response, filterChain);
|
testObj.doFilterInternal(request, response, filterChain);
|
||||||
verify(response, times(1)).setStatus(HttpStatus.SC_CONFLICT);
|
verify(response, times(1)).setStatus(HttpStatus.SC_CONFLICT);
|
||||||
|
|||||||
Reference in New Issue
Block a user