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
commit a7d5f834d5f6af5a05cbad540dee49fdb8ddb8bf Author: Arnold Galovics <[email protected]> AuthorDate: Thu Jun 1 14:27:06 2023 +0200 FINERACT-1724: EclipseLink upgrade fixes + Spring Security chain fixes --- build.gradle | 2 +- .../groovy/org.apache.fineract.dependencies.gradle | 5 +- .../src/main/resources/META-INF/spring.factories | 2 - ...rk.boot.autoconfigure.AutoConfiguration.imports | 1 + .../src/main/resources/META-INF/spring.factories | 2 - ...rk.boot.autoconfigure.AutoConfiguration.imports | 1 + .../src/main/resources/META-INF/spring.factories | 2 - ...rk.boot.autoconfigure.AutoConfiguration.imports | 1 + .../infrastructure/core/boot/FineractProfiles.java | 1 + .../core/config/FineractProperties.java | 36 +++++ .../core/config/OAuth2SecurityConfig.java | 67 +++++---- .../infrastructure/core/config/SecurityConfig.java | 155 +++++++++++++++------ .../core/config/TomcatConnectorCustomizer.java | 36 ----- .../diagnostics/jpa/DiagnosticsEntityManager.java | 3 +- .../security/SecurityFilterChainDiagnostics.java | 81 +++++++++++ .../core/filters/CorrelationHeaderFilter.java | 4 +- .../core/filters/IdempotencyStoreFilter.java | 8 +- .../core/filters/RequestResponseFilter.java | 4 +- .../core/filters/ResponseCorsFilter.java | 33 ++--- .../infrastructure/core/jersey/JerseyConfig.java | 2 +- .../filter/FineractInstanceModeApiFilter.java | 8 +- .../jobs/filter/LoanCOBApiFilter.java | 10 +- .../InsecureTwoFactorAuthenticationFilter.java | 12 +- .../TenantAwareBasicAuthenticationFilter.java | 125 ++++++++--------- .../filter/TwoFactorAuthenticationFilter.java | 20 +-- .../src/main/resources/META-INF/orm.xml | 16 +-- integration-tests/dependencies.gradle | 2 + .../bulkimport/importhandler/loan/Loan.xls | Bin 0 -> 2328576 bytes .../bulkimport/importhandler/office/Office.xls | Bin 0 -> 359936 bytes .../bulkimport/importhandler/savings/Savings.xls | Bin 0 -> 1843712 bytes 30 files changed, 393 insertions(+), 246 deletions(-) diff --git a/build.gradle b/build.gradle index 3705b7e25..257598e7f 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ buildscript { dependencies { classpath 'com.bmuschko:gradle-cargo-plugin:2.9.0' - classpath 'org.eclipse.persistence:eclipselink:4.0.1' + classpath 'org.eclipse.persistence:eclipselink:4.0.0' classpath 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' classpath 'com.google.cloud.tools:jib-layer-filter-extension-gradle:0.3.0' classpath 'org.apache.commons:commons-lang3:3.12.0' diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle index 9070653b9..f6caf1c4b 100644 --- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle +++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle @@ -50,7 +50,7 @@ dependencyManagement { dependency 'org.codehaus.janino:janino:3.1.9' - dependency 'org.eclipse.persistence:org.eclipse.persistence.jpa:4.0.1' + dependency 'org.eclipse.persistence:org.eclipse.persistence.jpa:4.0.0' dependency 'com.google.guava:guava:31.1-jre' dependency 'com.google.code.gson:gson:2.10.1' dependency 'com.google.truth:truth:1.1.3' @@ -64,6 +64,7 @@ dependencyManagement { dependency 'com.github.librepdf:openpdf:1.3.30' dependency ('org.mnode.ical4j:ical4j:3.2.11') { exclude 'com.sun.mail:javax.mail' + exclude 'org.codehaus.groovy:groovy' } dependency 'org.apache.commons:commons-csv:1.10.0' dependency 'org.quartz-scheduler:quartz:2.3.2' @@ -233,6 +234,8 @@ dependencyManagement { entry 'json-path' entry 'xml-path' } + dependency 'org.apache.groovy:groovy-xml:4.0.6' + dependency 'org.apache.groovy:groovy-json:4.0.6' dependency 'org.mapstruct:mapstruct:1.5.4.Final' dependency 'org.mapstruct:mapstruct-processor:1.5.4.Final' diff --git a/custom/acme/event/starter/src/main/resources/META-INF/spring.factories b/custom/acme/event/starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 28d54193a..000000000 --- a/custom/acme/event/starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.acme.fineract.event.starter.AcmeEventAutoConfiguration \ No newline at end of file diff --git a/custom/acme/event/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/acme/event/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..27bbb4632 --- /dev/null +++ b/custom/acme/event/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.acme.fineract.event.starter.AcmeEventAutoConfiguration \ No newline at end of file diff --git a/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories b/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index a3c5b7e58..000000000 --- a/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration \ No newline at end of file diff --git a/custom/acme/loan/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/acme/loan/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..a900f8060 --- /dev/null +++ b/custom/acme/loan/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration \ No newline at end of file diff --git a/custom/acme/note/starter/src/main/resources/META-INF/spring.factories b/custom/acme/note/starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index d3f0e811b..000000000 --- a/custom/acme/note/starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration \ No newline at end of file diff --git a/custom/acme/note/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/acme/note/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..c20803e4a --- /dev/null +++ b/custom/acme/note/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java index e07173efc..6d0757021 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/FineractProfiles.java @@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.core.boot; public final class FineractProfiles { public static final String LIQUIBASE_ONLY = "liquibase-only"; + public static final String DIAGNOSTICS = "diagnostics"; private FineractProfiles() {} } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 3afa01687..3ef3c4acb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -57,6 +57,7 @@ public class FineractProperties { private FineractQueryProperties query; private FineractApiProperties api; + private FineractSecurityProperties security; private FineractNotificationProperties notification; @@ -352,6 +353,41 @@ public class FineractProperties { private boolean errorNotFoundFail; } + @Getter + @Setter + public static class FineractSecurityProperties { + + private FineractSecurityBasicAuth basicauth; + private FineractSecurityTwoFactorAuth twoFactor; + private FineractSecurityOAuth oauth; + + public void set2fa(FineractSecurityTwoFactorAuth twoFactor) { + this.twoFactor = twoFactor; + } + } + + @Getter + @Setter + public static class FineractSecurityBasicAuth { + + private boolean enabled; + } + + @Getter + @Setter + public static class FineractSecurityTwoFactorAuth { + + private boolean enabled; + } + + @Getter + @Setter + public static class FineractSecurityOAuth { + + private boolean enabled; + } + + @Getter @Setter public static class FineractTransactionProcessorItemProperties { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java index 9a6c59213..37caef0a9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java @@ -25,15 +25,21 @@ import static org.springframework.security.authorization.AuthorityAuthorizationM import static org.springframework.security.authorization.AuthorizationManagers.allOf; import java.util.Collection; +import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; +import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.exceptionmapper.OAuth2ExceptionEntryPoint; +import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken; +import org.apache.fineract.infrastructure.security.data.PlatformRequestLog; import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter; import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; +import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService; +import org.apache.fineract.infrastructure.security.service.TwoFactorService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; @@ -55,25 +61,36 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.context.SecurityContextHolderFilter; -@Configuration +@Configuration(proxyBeanMethods = false) @ConditionalOnProperty("fineract.security.oauth.enabled") @EnableMethodSecurity public class OAuth2SecurityConfig { @Autowired - private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter; + private TenantAwareJpaPlatformUserDetailsService userDetailsService; @Autowired - private TenantAwareTenantIdentifierFilter tenantAwareTenantIdentifierFilter; + private ServerProperties serverProperties; @Autowired - private TenantAwareJpaPlatformUserDetailsService userDetailsService; + private TwoFactorService twoFactorService; @Autowired - private ServerProperties serverProperties; + private BasicAuthTenantDetailsService basicAuthTenantDetailsService; - private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + @Autowired + private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; + @Autowired + private ConfigurationDomainService configurationDomainService; + + @Autowired + private CacheWritePlatformService cacheWritePlatformService; + + @Autowired + private BusinessDateReadPlatformService businessDateReadPlatformService; + + private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); @Bean public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception { @@ -90,26 +107,40 @@ public class OAuth2SecurityConfig { .requestMatchers("/api/*/twofactor").fullyAuthenticated() // .requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"), selfServiceUserAuthManager())); // }); + + if (serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); + } + return http.build(); } @Bean - public SecurityFilterChain FilterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients .exceptionHandling((ehc) -> ehc.authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter())) .authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) // .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // - .addFilterAfter(tenantAwareTenantIdentifierFilter, SecurityContextHolderFilter.class) // - .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class); // + .addFilterAfter(tenantAwareTenantIdentifierFilter(), SecurityContextHolderFilter.class) // + .addFilterAfter(twoFactorAuthenticationFilter(), BasicAuthenticationFilter.class); // if (serverProperties.getSsl().isEnabled()) { http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); } + return http.build(); } + public TenantAwareTenantIdentifierFilter tenantAwareTenantIdentifierFilter() { + return new TenantAwareTenantIdentifierFilter(basicAuthTenantDetailsService, toApiJsonSerializer, configurationDomainService, cacheWritePlatformService, businessDateReadPlatformService); + } + + public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() { + return new TwoFactorAuthenticationFilter(twoFactorService); + } + @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); @@ -127,20 +158,4 @@ public class OAuth2SecurityConfig { } }; } - - @Bean - public FilterRegistrationBean<TenantAwareTenantIdentifierFilter> tenantAwareTenantIdentifierFilterRegistration() throws Exception { - FilterRegistrationBean<TenantAwareTenantIdentifierFilter> registration = new FilterRegistrationBean<TenantAwareTenantIdentifierFilter>( - tenantAwareTenantIdentifierFilter); - registration.setEnabled(false); - return registration; - } - - @Bean - public FilterRegistrationBean<TwoFactorAuthenticationFilter> twoFactorAuthenticationFilterRegistration() { - FilterRegistrationBean<TwoFactorAuthenticationFilter> registration = new FilterRegistrationBean<TwoFactorAuthenticationFilter>( - twoFactorAuthenticationFilter); - registration.setEnabled(false); - return registration; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java index a1899b5c2..7fb093c6b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java @@ -23,17 +23,38 @@ import static org.springframework.security.authorization.AuthenticatedAuthorizat import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority; import static org.springframework.security.authorization.AuthorizationManagers.allOf; +import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; +import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.commands.domain.CommandSourceRepository; +import org.apache.fineract.commands.service.CommandSourceService; +import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; +import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; +import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter; import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter; +import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter; +import org.apache.fineract.infrastructure.core.filters.ResponseCorsFilter; +import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.MDCWrapper; import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter; import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter; +import org.apache.fineract.infrastructure.security.data.PlatformRequestLog; import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter; import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter; import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; +import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService; +import org.apache.fineract.infrastructure.security.service.TwoFactorService; +import org.apache.fineract.notification.service.UserNotificationService; +import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -48,24 +69,18 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.context.SecurityContextHolderFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.transaction.PlatformTransactionManager; -@Configuration +@Configuration(proxyBeanMethods = false) @ConditionalOnProperty("fineract.security.basicauth.enabled") @EnableMethodSecurity public class SecurityConfig { - - @Autowired - private TenantAwareJpaPlatformUserDetailsService userDetailsService; - @Autowired - private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter; + private ApplicationContext applicationContext; @Autowired - private FineractInstanceModeApiFilter fineractInstanceModeApiFilter; - @Autowired - private LoanCOBApiFilter loanCOBApiFilter; + private TenantAwareJpaPlatformUserDetailsService userDetailsService; @Autowired private FineractProperties fineractProperties; @@ -74,8 +89,40 @@ public class SecurityConfig { private ServerProperties serverProperties; @Autowired - private IdempotencyStoreFilter idempotencyStoreFilter; + private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; + @Autowired + private ConfigurationDomainService configurationDomainService; + @Autowired + private CacheWritePlatformService cacheWritePlatformService; + @Autowired + private UserNotificationService userNotificationService; + @Autowired + private BasicAuthTenantDetailsService basicAuthTenantDetailsService; + @Autowired + private BusinessDateReadPlatformService businessDateReadPlatformService; + @Autowired + private MDCWrapper mdcWrapper; + @Autowired + private CommandSourceRepository commandSourceRepository; + @Autowired + private CommandSourceService commandSourceService; + @Autowired + private FineractRequestContextHolder fineractRequestContextHolder; + @Autowired + private GLIMAccountInfoRepository glimAccountInfoRepository; + @Autowired + private LoanAccountLockService loanAccountLockService; + @Autowired + private PlatformSecurityContext context; + @Autowired + private InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService; + @Autowired + private LoanRepository loanRepository; + @Autowired + private LoanRescheduleRequestRepository loanRescheduleRequestRepository; + @Autowired + private PlatformTransactionManager transactionManager; @Bean public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception { @@ -93,7 +140,11 @@ public class SecurityConfig { .requestMatchers("/api/*/twofactor").fullyAuthenticated() // .requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); // }) // - .httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())); // + .httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())); + + if (serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); + } return http.build(); } @@ -101,22 +152,65 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients + .securityContext((securityContext) -> securityContext.requireExplicitSave(false)) .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // - .addFilterAfter(fineractInstanceModeApiFilter, SecurityContextHolderFilter.class) // + .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) + .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) + .addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) // + .addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) // .addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) // - .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class) // - .addFilterAfter(loanCOBApiFilter, InsecureTwoFactorAuthenticationFilter.class) // - .addFilterBefore(idempotencyStoreFilter, ExceptionTranslationFilter.class); // + .addFilterAfter(loanCOBApiFilter(), TenantAwareBasicAuthenticationFilter.class) // + .addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); // - if (serverProperties.getSsl().isEnabled()) { - http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); + if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) { + http.addFilterAfter(twoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class); + } else { + http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class); } + return http.build(); } - @Bean + public RequestResponseFilter requestResponseFilter() { + return new RequestResponseFilter(); + } + + public LoanCOBApiFilter loanCOBApiFilter() { + return new LoanCOBApiFilter(glimAccountInfoRepository, loanAccountLockService, context, inlineLoanCOBExecutorService, loanRepository, fineractProperties, loanRescheduleRequestRepository, transactionManager); + } + + public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() { + TwoFactorService twoFactorService = applicationContext.getBean(TwoFactorService.class); + return new TwoFactorAuthenticationFilter(twoFactorService); + } + + public InsecureTwoFactorAuthenticationFilter insecureTwoFactorAuthenticationFilter() { + return new InsecureTwoFactorAuthenticationFilter(); + } + + public FineractInstanceModeApiFilter fineractInstanceModeApiFilter() { + return new FineractInstanceModeApiFilter(fineractProperties); + } + + public IdempotencyStoreFilter idempotencyStoreFilter() { + return new IdempotencyStoreFilter(commandSourceRepository, commandSourceService, fineractProperties, fineractRequestContextHolder); + } + + public CorrelationHeaderFilter correlationHeaderFilter() { + return new CorrelationHeaderFilter(fineractProperties, mdcWrapper); + } + + public ResponseCorsFilter responseCorsFilter() { + return new ResponseCorsFilter(); + } + public TenantAwareBasicAuthenticationFilter tenantAwareBasicAuthenticationFilter() throws Exception { - return new TenantAwareBasicAuthenticationFilter(authenticationManagerBean(), basicAuthenticationEntryPoint()); + TenantAwareBasicAuthenticationFilter filter = new TenantAwareBasicAuthenticationFilter(authenticationManagerBean(), basicAuthenticationEntryPoint(), + toApiJsonSerializer, configurationDomainService, cacheWritePlatformService, + userNotificationService, basicAuthTenantDetailsService, + businessDateReadPlatformService); + filter.setRequestMatcher(AntPathRequestMatcher.antMatcher("/api/**")); + return filter; } @Bean @@ -145,21 +239,4 @@ public class SecurityConfig { providerManager.setEraseCredentialsAfterAuthentication(false); return providerManager; } - - @Bean - public FilterRegistrationBean<TenantAwareBasicAuthenticationFilter> tenantAwareBasicAuthenticationFilterRegistration() - throws Exception { - FilterRegistrationBean<TenantAwareBasicAuthenticationFilter> registration = new FilterRegistrationBean<TenantAwareBasicAuthenticationFilter>( - tenantAwareBasicAuthenticationFilter()); - registration.setEnabled(false); - return registration; - } - - @Bean - public FilterRegistrationBean<TwoFactorAuthenticationFilter> twoFactorAuthenticationFilterRegistration() { - FilterRegistrationBean<TwoFactorAuthenticationFilter> registration = new FilterRegistrationBean<TwoFactorAuthenticationFilter>( - twoFactorAuthenticationFilter); - registration.setEnabled(false); - return registration; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TomcatConnectorCustomizer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TomcatConnectorCustomizer.java deleted file mode 100644 index ef7576b48..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TomcatConnectorCustomizer.java +++ /dev/null @@ -1,36 +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.core.config; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.server.WebServerFactoryCustomizer; -import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; -import org.springframework.stereotype.Component; - -@Component -@ConditionalOnClass(name = "org.apache.coyote.http11.Http11Nio2Protocol") -public class TomcatConnectorCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> { - - @Override - public void customize(ConfigurableServletWebServerFactory factory) { - ((TomcatServletWebServerFactory) factory).setProtocol("org.apache.coyote.http11.Http11Nio2Protocol"); - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java index bc864a2ba..8975730f2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java @@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.core.diagnostics.jpa; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jakarta.persistence.EntityManagerFactory; +import org.apache.fineract.infrastructure.core.boot.FineractProfiles; import org.eclipse.persistence.internal.jpa.EntityManagerImpl; import org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet; import org.springframework.beans.BeansException; @@ -38,7 +39,7 @@ import org.springframework.stereotype.Component; * <br> * To enable this, run Fineract with the <b>diagnostics</b> profile. */ -@Profile("diagnostics") +@Profile(FineractProfiles.DIAGNOSTICS) @Component public class DiagnosticsEntityManager implements ApplicationContextAware { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/security/SecurityFilterChainDiagnostics.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/security/SecurityFilterChainDiagnostics.java new file mode 100644 index 000000000..3c5f90043 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/security/SecurityFilterChainDiagnostics.java @@ -0,0 +1,81 @@ +/** + * 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.core.diagnostics.security; + +import static java.lang.System.lineSeparator; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.boot.FineractProfiles; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Profile; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.stereotype.Component; + +@Component +@Profile(FineractProfiles.DIAGNOSTICS) +@Slf4j +@RequiredArgsConstructor +public class SecurityFilterChainDiagnostics implements InitializingBean { + private final List<SecurityFilterChain> filterChains; + + @Override + public void afterPropertiesSet() throws Exception { + filterChains.forEach(this::printFilterChain); + } + + private void printFilterChain(SecurityFilterChain filterChain) { + if (filterChain instanceof DefaultSecurityFilterChain) { + printDefaultFilterChain((DefaultSecurityFilterChain) filterChain); + } else { + printUnknownFilterChain(filterChain); + } + } + + private void printDefaultFilterChain(DefaultSecurityFilterChain filterChain) { + + log.info( + """ + + Filter chain matcher: %s + Filters in order: + %s + """.formatted(filterChain.getRequestMatcher(), getFormattedFilters(filterChain))); + } + + private String getFormattedFilters(DefaultSecurityFilterChain filterChain) { + StringBuilder result = new StringBuilder(); + filterChain.getFilters().forEach(f -> { + result.append("- "); + result.append(f.getClass().getName()); + result.append(lineSeparator()); + }); + return result.toString(); + } + + private void printUnknownFilterChain(SecurityFilterChain filterChain) { + log.info( + """ + Filter chain: + %s + """.formatted(filterChain)); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java index 459464830..5797a8cf9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java @@ -19,23 +19,21 @@ package org.apache.fineract.infrastructure.core.filters; -import java.io.IOException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; 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.core.service.MDCWrapper; import org.apache.fineract.infrastructure.security.utils.LogParameterEscapeUtil; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @RequiredArgsConstructor @Slf4j -@Component public class CorrelationHeaderFilter extends OncePerRequestFilter { public static final String CORRELATION_ID_KEY = "correlationId"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java index 0bfdf4f9e..6270b8e20 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java @@ -18,14 +18,14 @@ */ package org.apache.fineract.infrastructure.core.filters; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Optional; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.ws.rs.core.UriInfo; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.mutable.Mutable; @@ -39,13 +39,11 @@ import org.apache.fineract.commands.service.SynchronousCommandProcessingService; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.jetbrains.annotations.NotNull; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingResponseWrapper; @RequiredArgsConstructor @Slf4j -@Component public class IdempotencyStoreFilter extends OncePerRequestFilter implements BatchFilter { private final CommandSourceRepository commandSourceRepository; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/RequestResponseFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/RequestResponseFilter.java index e900ca5f6..659efa861 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/RequestResponseFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/RequestResponseFilter.java @@ -19,19 +19,17 @@ package org.apache.fineract.infrastructure.core.filters; -import java.io.IOException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @RequiredArgsConstructor @Slf4j -@Component public class RequestResponseFilter extends OncePerRequestFilter { @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java index 870464c4d..73c3dd580 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/ResponseCorsFilter.java @@ -18,36 +18,31 @@ */ package org.apache.fineract.infrastructure.core.filters; -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.container.ContainerResponseContext; -import jakarta.ws.rs.container.ContainerResponseFilter; -import jakarta.ws.rs.ext.Provider; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; /** * Filter that returns a response with headers that allows for Cross-Origin Requests (CORs) to be performed against the * platform API. */ -@Provider -@Component -@Scope("singleton") -public class ResponseCorsFilter implements ContainerResponseFilter { +public class ResponseCorsFilter extends OncePerRequestFilter { @Override - public void filter(final ContainerRequestContext request, final ContainerResponseContext response) { + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - response.getHeaders().add("Access-Control-Allow-Origin", "*"); - // .header("Access-Control-Expose-Headers", - // "Fineract-Platform-TenantId") - response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + final String reqHead = request.getHeader("Access-Control-Request-Headers"); - final String reqHead = request.getHeaders().getFirst("Access-Control-Request-Headers"); - - if (null != reqHead && StringUtils.hasText(reqHead)) { - response.getHeaders().add("Access-Control-Allow-Headers", reqHead); + if (StringUtils.hasText(reqHead)) { + response.addHeader("Access-Control-Allow-Headers", reqHead); } + filterChain.doFilter(request, response); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/jersey/JerseyConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/jersey/JerseyConfig.java index aaa1343ce..c5a18f0c0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/jersey/JerseyConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/jersey/JerseyConfig.java @@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; -@Configuration +@Configuration(proxyBeanMethods = false) @ApplicationPath("/api") public class JerseyConfig extends ResourceConfig { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java index d940e4430..21db95724 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/instancemode/filter/FineractInstanceModeApiFilter.java @@ -21,23 +21,21 @@ package org.apache.fineract.infrastructure.instancemode.filter; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter.ExceptionListItem.item; -import java.io.IOException; -import java.util.List; -import java.util.function.Function; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.ws.rs.HttpMethod; +import java.io.IOException; +import java.util.List; +import java.util.function.Function; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; import org.apache.http.HttpStatus; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -@Component @RequiredArgsConstructor public class FineractInstanceModeApiFilter extends OncePerRequestFilter { 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 356121259..19d51caa0 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 @@ -22,6 +22,10 @@ import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeU import com.google.common.collect.Lists; import io.github.resilience4j.core.functions.Either; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; @@ -29,10 +33,6 @@ import java.util.Collections; import java.util.List; import java.util.function.Predicate; import java.util.regex.Pattern; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -57,13 +57,11 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException; import org.apache.http.HttpStatus; import org.springframework.http.HttpMethod; -import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.filter.OncePerRequestFilter; -@Component @RequiredArgsConstructor public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchRequestPreprocessor { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java index 7a1ea837c..85ef766fd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/InsecureTwoFactorAuthenticationFilter.java @@ -18,16 +18,15 @@ */ package org.apache.fineract.infrastructure.security.filter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -35,15 +34,12 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Service; /** * A dummy {@link TwoFactorAuthenticationFilter} filter used when 'twofactor' environment profile is not active. * * This filter adds 'TWOFACTOR_AUTHENTICATED' authority to every authenticated platform user. */ -@Service -@ConditionalOnProperty(name = "fineract.security.2fa.enabled", havingValue = "false") public class InsecureTwoFactorAuthenticationFilter extends TwoFactorAuthenticationFilter { public InsecureTwoFactorAuthenticationFilter() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java index ffdc2ed33..b86182b80 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java @@ -18,13 +18,15 @@ */ package org.apache.fineract.infrastructure.security.filter; -import java.io.IOException; -import java.time.LocalDate; -import java.util.HashMap; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDate; +import java.util.HashMap; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.StopWatch; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; @@ -39,16 +41,14 @@ import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdenti import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; import org.apache.fineract.notification.service.UserNotificationService; import org.apache.fineract.useradministration.domain.AppUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.util.matcher.AnyRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; /** * A customised version of spring security's {@link BasicAuthenticationFilter}. @@ -63,36 +63,32 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi * If multi-tenant and basic auth credentials are invalid, a http error response is returned. */ -@ConditionalOnProperty("fineract.security.basicauth.enabled") +@Slf4j public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter { private static boolean firstRequestProcessed = false; - private static final Logger LOG = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class); - - @Autowired - private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; - - @Autowired - private ConfigurationDomainService configurationDomainService; - - @Autowired - private CacheWritePlatformService cacheWritePlatformService; - - @Autowired - private UserNotificationService userNotificationService; - - @Autowired - private BasicAuthTenantDetailsService basicAuthTenantDetailsService; - - @Autowired - private BusinessDateReadPlatformService businessDateReadPlatformService; + private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; + private final ConfigurationDomainService configurationDomainService; + private final CacheWritePlatformService cacheWritePlatformService; + private final UserNotificationService userNotificationService; + private final BasicAuthTenantDetailsService basicAuthTenantDetailsService; + private final BusinessDateReadPlatformService businessDateReadPlatformService; private final String tenantRequestHeader = "Fineract-Platform-TenantId"; private final boolean exceptionIfHeaderMissing = true; + @Setter + private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE; + public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager, - final AuthenticationEntryPoint authenticationEntryPoint) { + final AuthenticationEntryPoint authenticationEntryPoint, ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer, ConfigurationDomainService configurationDomainService, CacheWritePlatformService cacheWritePlatformService, UserNotificationService userNotificationService, BasicAuthTenantDetailsService basicAuthTenantDetailsService, BusinessDateReadPlatformService businessDateReadPlatformService) { super(authenticationManager, authenticationEntryPoint); + this.toApiJsonSerializer = toApiJsonSerializer; + this.configurationDomainService = configurationDomainService; + this.cacheWritePlatformService = cacheWritePlatformService; + this.userNotificationService = userNotificationService; + this.basicAuthTenantDetailsService = basicAuthTenantDetailsService; + this.businessDateReadPlatformService = businessDateReadPlatformService; } @Override @@ -108,48 +104,49 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil // ignore to allow 'preflight' requests from AJAX applications // in different origin (domain name) } else { + if (requestMatcher.matches(request)) { + String tenantIdentifier = request.getHeader(this.tenantRequestHeader); - String tenantIdentifier = request.getHeader(this.tenantRequestHeader); - - if (org.apache.commons.lang3.StringUtils.isBlank(tenantIdentifier)) { - tenantIdentifier = request.getParameter("tenantIdentifier"); - } - - if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { - throw new InvalidTenantIdentifierException("No tenant identifier found: Add request header of '" - + this.tenantRequestHeader + "' or add the parameter 'tenantIdentifier' to query string of request URL."); - } + if (org.apache.commons.lang3.StringUtils.isBlank(tenantIdentifier)) { + tenantIdentifier = request.getParameter("tenantIdentifier"); + } - String pathInfo = request.getRequestURI(); - boolean isReportRequest = false; - if (pathInfo != null && pathInfo.contains("report")) { - isReportRequest = true; - } - final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest); - ThreadLocalContextUtil.setTenant(tenant); - HashMap<BusinessDateType, LocalDate> businessDates = this.businessDateReadPlatformService.getBusinessDates(); - ThreadLocalContextUtil.setBusinessDates(businessDates); - String authToken = request.getHeader("Authorization"); - - if (authToken != null && authToken.startsWith("Basic ")) { - ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("Basic ", "")); - } + if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { + throw new InvalidTenantIdentifierException("No tenant identifier found: Add request header of '" + + this.tenantRequestHeader + "' or add the parameter 'tenantIdentifier' to query string of request URL."); + } - if (!firstRequestProcessed) { - final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/"); - System.setProperty("baseUrl", baseUrl); + String pathInfo = request.getRequestURI(); + boolean isReportRequest = false; + if (pathInfo != null && pathInfo.contains("report")) { + isReportRequest = true; + } + final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest); + ThreadLocalContextUtil.setTenant(tenant); + HashMap<BusinessDateType, LocalDate> businessDates = this.businessDateReadPlatformService.getBusinessDates(); + ThreadLocalContextUtil.setBusinessDates(businessDates); + String authToken = request.getHeader("Authorization"); + + if (authToken != null && authToken.startsWith("Basic ")) { + ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("Basic ", "")); + } - final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled(); - if (ehcacheEnabled) { - this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE); - } else { - this.cacheWritePlatformService.switchToCache(CacheType.NO_CACHE); + if (!firstRequestProcessed) { + final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/"); + System.setProperty("baseUrl", baseUrl); + + final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled(); + if (ehcacheEnabled) { + this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE); + } else { + this.cacheWritePlatformService.switchToCache(CacheType.NO_CACHE); + } + TenantAwareBasicAuthenticationFilter.firstRequestProcessed = true; } - TenantAwareBasicAuthenticationFilter.firstRequestProcessed = true; } - } - super.doFilterInternal(request, response, filterChain); + super.doFilterInternal(request, response, filterChain); + } } catch (final InvalidTenantIdentifierException e) { // deal with exception at low level SecurityContextHolder.getContext().setAuthentication(null); @@ -160,7 +157,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil ThreadLocalContextUtil.reset(); task.stop(); final PlatformRequestLog log = PlatformRequestLog.from(task, request); - LOG.debug("{}", this.toApiJsonSerializer.serialize(log)); + TenantAwareBasicAuthenticationFilter.log.debug("{}", this.toApiJsonSerializer.serialize(log)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java index f025df54f..6c6d382a5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TwoFactorAuthenticationFilter.java @@ -18,23 +18,22 @@ */ package org.apache.fineract.infrastructure.security.filter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants; import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken; import org.apache.fineract.infrastructure.security.domain.TFAccessToken; import org.apache.fineract.infrastructure.security.service.TwoFactorService; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -42,7 +41,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Service; import org.springframework.web.filter.GenericFilterBean; /** @@ -56,17 +54,11 @@ import org.springframework.web.filter.GenericFilterBean; * An authenticated platform user with permission 'BYPASS_TWOFACTOR' will always be granted 'TWOFACTOR_AUTHENTICATED' * authority regardless of the value of the 'Fineract-Platform-TFA-Token' header. */ -@Service -@ConditionalOnProperty("fineract.security.2fa.enabled") +@RequiredArgsConstructor public class TwoFactorAuthenticationFilter extends GenericFilterBean { private final TwoFactorService twoFactorService; - @Autowired - public TwoFactorAuthenticationFilter(TwoFactorService twoFactorService) { - this.twoFactorService = twoFactorService; - } - @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { diff --git a/fineract-provider/src/main/resources/META-INF/orm.xml b/fineract-provider/src/main/resources/META-INF/orm.xml index b54608dee..4f05f84e6 100644 --- a/fineract-provider/src/main/resources/META-INF/orm.xml +++ b/fineract-provider/src/main/resources/META-INF/orm.xml @@ -21,18 +21,18 @@ --> -<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd" - version="2.0"> +<entity-mappings xmlns="https://jakarta.ee/xml/ns/persistence/orm" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm + https://jakarta.ee/xml/ns/persistence/orm/orm_3_0.xsd" version="3.0"> <persistence-unit-metadata> <persistence-unit-defaults> <delimited-identifiers/> -<!-- <entity-listeners>--> -<!-- <entity-listener--> -<!-- class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />--> -<!-- </entity-listeners>--> + <entity-listeners> + <entity-listener + class="org.springframework.data.jpa.domain.support.AuditingEntityListener"/> + </entity-listeners> </persistence-unit-defaults> </persistence-unit-metadata> diff --git a/integration-tests/dependencies.gradle b/integration-tests/dependencies.gradle index b46b15b7a..ee05c9657 100644 --- a/integration-tests/dependencies.gradle +++ b/integration-tests/dependencies.gradle @@ -42,6 +42,8 @@ dependencies { exclude group: 'org.apache.sling' exclude group: 'com.sun.xml.bind' } + testImplementation 'org.apache.groovy:groovy-xml' + testImplementation 'org.apache.groovy:groovy-json' testImplementation 'org.awaitility:awaitility' testCompileOnly 'org.projectlombok:lombok' diff --git a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/loan/Loan.xls b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/loan/Loan.xls new file mode 100644 index 000000000..a7c87b5bd Binary files /dev/null and b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/loan/Loan.xls differ diff --git a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/office/Office.xls b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/office/Office.xls new file mode 100644 index 000000000..3a63e1221 Binary files /dev/null and b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/office/Office.xls differ diff --git a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/savings/Savings.xls b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/savings/Savings.xls new file mode 100644 index 000000000..ec42864d5 Binary files /dev/null and b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/savings/Savings.xls differ
