You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/11/02 10:57:37 UTC

[fineract] branch develop updated: [FINERACT-1678] Bypass LoanAccount Write Protection

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new cce790205 [FINERACT-1678] Bypass LoanAccount Write Protection
cce790205 is described below

commit cce79020544f34e7bd607a05d27be124a4bfa24a
Author: taskain7 <ta...@gmail.com>
AuthorDate: Mon Oct 17 12:12:48 2022 +0200

    [FINERACT-1678] Bypass LoanAccount Write Protection
---
 .../service/InlineLoanCOBExecutorServiceImpl.java  |  8 ++++-
 .../jobs/filter/LoanCOBApiFilter.java              |  8 ++++-
 .../useradministration/domain/AppUser.java         |  4 +++
 .../db/changelog/tenant/changelog-tenant.xml       |  1 +
 ...dd_bypass_loan_write_transaction_permission.xml | 34 ++++++++++++++++++++++
 .../jobs/filter/LoanCOBApiFilterTest.java          | 30 +++++++++++++++++++
 6 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
index b8e10bfaa..2c50ec66a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
@@ -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();
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
index dea636aa0..08e33e286 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
@@ -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);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
index 0e9133c83..36c1f77ea 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
@@ -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;
     }
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index f652ebe41..f0f92f568 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -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>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0065_add_bypass_loan_write_transaction_permission.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0065_add_bypass_loan_write_transaction_permission.xml
new file mode 100644
index 000000000..c411b5ec4
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0065_add_bypass_loan_write_transaction_permission.xml
@@ -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>
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
index de7bf1ce1..216c670a4 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
@@ -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);
+        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);