FINERACT-1724: EclipseLink upgrade fixes + Spring Security chain fixes

This commit is contained in:
Arnold Galovics
2023-06-06 11:45:08 +02:00
committed by Arnold Gálovics
parent abb5d4abb5
commit a7d5f834d5
29 changed files with 404 additions and 257 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.bmuschko:gradle-cargo-plugin:2.9.0' 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 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0'
classpath 'com.google.cloud.tools:jib-layer-filter-extension-gradle:0.3.0' classpath 'com.google.cloud.tools:jib-layer-filter-extension-gradle:0.3.0'
classpath 'org.apache.commons:commons-lang3:3.12.0' classpath 'org.apache.commons:commons-lang3:3.12.0'
@@ -50,7 +50,7 @@ dependencyManagement {
dependency 'org.codehaus.janino:janino:3.1.9' 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.guava:guava:31.1-jre'
dependency 'com.google.code.gson:gson:2.10.1' dependency 'com.google.code.gson:gson:2.10.1'
dependency 'com.google.truth:truth:1.1.3' dependency 'com.google.truth:truth:1.1.3'
@@ -64,6 +64,7 @@ dependencyManagement {
dependency 'com.github.librepdf:openpdf:1.3.30' dependency 'com.github.librepdf:openpdf:1.3.30'
dependency ('org.mnode.ical4j:ical4j:3.2.11') { dependency ('org.mnode.ical4j:ical4j:3.2.11') {
exclude 'com.sun.mail:javax.mail' exclude 'com.sun.mail:javax.mail'
exclude 'org.codehaus.groovy:groovy'
} }
dependency 'org.apache.commons:commons-csv:1.10.0' dependency 'org.apache.commons:commons-csv:1.10.0'
dependency 'org.quartz-scheduler:quartz:2.3.2' dependency 'org.quartz-scheduler:quartz:2.3.2'
@@ -233,6 +234,8 @@ dependencyManagement {
entry 'json-path' entry 'json-path'
entry 'xml-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:1.5.4.Final'
dependency 'org.mapstruct:mapstruct-processor:1.5.4.Final' dependency 'org.mapstruct:mapstruct-processor:1.5.4.Final'
@@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.fineract.event.starter.AcmeEventAutoConfiguration
@@ -0,0 +1 @@
com.acme.fineract.event.starter.AcmeEventAutoConfiguration
@@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration
@@ -0,0 +1 @@
com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration
@@ -1,2 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.core.boot;
public final class FineractProfiles { public final class FineractProfiles {
public static final String LIQUIBASE_ONLY = "liquibase-only"; public static final String LIQUIBASE_ONLY = "liquibase-only";
public static final String DIAGNOSTICS = "diagnostics";
private FineractProfiles() {} private FineractProfiles() {}
} }
@@ -57,6 +57,7 @@ public class FineractProperties {
private FineractQueryProperties query; private FineractQueryProperties query;
private FineractApiProperties api; private FineractApiProperties api;
private FineractSecurityProperties security;
private FineractNotificationProperties notification; private FineractNotificationProperties notification;
@@ -352,6 +353,41 @@ public class FineractProperties {
private boolean errorNotFoundFail; 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 @Getter
@Setter @Setter
public static class FineractTransactionProcessorItemProperties { public static class FineractTransactionProcessorItemProperties {
@@ -25,15 +25,21 @@ import static org.springframework.security.authorization.AuthorityAuthorizationM
import static org.springframework.security.authorization.AuthorizationManagers.allOf; import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import java.util.Collection; 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.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.FineractJwtAuthenticationToken;
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter; import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter;
import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; 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.TenantAwareJpaPlatformUserDetailsService;
import org.apache.fineract.infrastructure.security.service.TwoFactorService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter; 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.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter; import org.springframework.security.web.context.SecurityContextHolderFilter;
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("fineract.security.oauth.enabled") @ConditionalOnProperty("fineract.security.oauth.enabled")
@EnableMethodSecurity @EnableMethodSecurity
public class OAuth2SecurityConfig { public class OAuth2SecurityConfig {
@Autowired
private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter;
@Autowired
private TenantAwareTenantIdentifierFilter tenantAwareTenantIdentifierFilter;
@Autowired @Autowired
private TenantAwareJpaPlatformUserDetailsService userDetailsService; private TenantAwareJpaPlatformUserDetailsService userDetailsService;
@Autowired @Autowired
private ServerProperties serverProperties; private ServerProperties serverProperties;
private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); @Autowired
private TwoFactorService twoFactorService;
@Autowired
private BasicAuthTenantDetailsService basicAuthTenantDetailsService;
@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 @Bean
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
@@ -90,26 +107,40 @@ public class OAuth2SecurityConfig {
.requestMatchers("/api/*/twofactor").fullyAuthenticated() // .requestMatchers("/api/*/twofactor").fullyAuthenticated() //
.requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"), selfServiceUserAuthManager())); // .requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"), selfServiceUserAuthManager())); //
}); });
if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
}
return http.build(); return http.build();
} }
@Bean @Bean
public SecurityFilterChain FilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http // http //
.csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
.exceptionHandling((ehc) -> ehc.authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) .exceptionHandling((ehc) -> ehc.authenticationEntryPoint(new OAuth2ExceptionEntryPoint()))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter())) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter()))
.authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) // .authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) //
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
.addFilterAfter(tenantAwareTenantIdentifierFilter, SecurityContextHolderFilter.class) // .addFilterAfter(tenantAwareTenantIdentifierFilter(), SecurityContextHolderFilter.class) //
.addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class); // .addFilterAfter(twoFactorAuthenticationFilter(), BasicAuthenticationFilter.class); //
if (serverProperties.getSsl().isEnabled()) { if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
} }
return http.build(); return http.build();
} }
public TenantAwareTenantIdentifierFilter tenantAwareTenantIdentifierFilter() {
return new TenantAwareTenantIdentifierFilter(basicAuthTenantDetailsService, toApiJsonSerializer, configurationDomainService, cacheWritePlatformService, businessDateReadPlatformService);
}
public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
return new TwoFactorAuthenticationFilter(twoFactorService);
}
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 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;
}
} }
@@ -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.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorizationManagers.allOf; 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.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.instancemode.filter.FineractInstanceModeApiFilter;
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter; 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.InsecureTwoFactorAuthenticationFilter;
import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter; import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; 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.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.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -48,25 +69,19 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.context.SecurityContextHolderFilter; import org.springframework.transaction.PlatformTransactionManager;
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("fineract.security.basicauth.enabled") @ConditionalOnProperty("fineract.security.basicauth.enabled")
@EnableMethodSecurity @EnableMethodSecurity
public class SecurityConfig { public class SecurityConfig {
@Autowired
private ApplicationContext applicationContext;
@Autowired @Autowired
private TenantAwareJpaPlatformUserDetailsService userDetailsService; private TenantAwareJpaPlatformUserDetailsService userDetailsService;
@Autowired
private TwoFactorAuthenticationFilter twoFactorAuthenticationFilter;
@Autowired
private FineractInstanceModeApiFilter fineractInstanceModeApiFilter;
@Autowired
private LoanCOBApiFilter loanCOBApiFilter;
@Autowired @Autowired
private FineractProperties fineractProperties; private FineractProperties fineractProperties;
@@ -74,8 +89,40 @@ public class SecurityConfig {
private ServerProperties serverProperties; private ServerProperties serverProperties;
@Autowired @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 @Bean
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
@@ -93,20 +140,7 @@ public class SecurityConfig {
.requestMatchers("/api/*/twofactor").fullyAuthenticated() // .requestMatchers("/api/*/twofactor").fullyAuthenticated() //
.requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); // .requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); //
}) // }) //
.httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())); // .httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint()));
return http.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http //
.csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
.addFilterAfter(fineractInstanceModeApiFilter, SecurityContextHolderFilter.class) //
.addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
.addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class) //
.addFilterAfter(loanCOBApiFilter, InsecureTwoFactorAuthenticationFilter.class) //
.addFilterBefore(idempotencyStoreFilter, ExceptionTranslationFilter.class); //
if (serverProperties.getSsl().isEnabled()) { if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
@@ -115,8 +149,68 @@ public class SecurityConfig {
} }
@Bean @Bean
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(requestResponseFilter(), ExceptionTranslationFilter.class)
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class)
.addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) //
.addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) //
.addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
.addFilterAfter(loanCOBApiFilter(), TenantAwareBasicAuthenticationFilter.class) //
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
http.addFilterAfter(twoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class);
} else {
http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class);
}
return http.build();
}
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 { 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 @Bean
@@ -145,21 +239,4 @@ public class SecurityConfig {
providerManager.setEraseCredentialsAfterAuthentication(false); providerManager.setEraseCredentialsAfterAuthentication(false);
return providerManager; 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;
}
} }
@@ -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");
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.core.diagnostics.jpa;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityManagerFactory;
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import org.eclipse.persistence.internal.jpa.EntityManagerImpl; import org.eclipse.persistence.internal.jpa.EntityManagerImpl;
import org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet; import org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@@ -38,7 +39,7 @@ import org.springframework.stereotype.Component;
* <br> * <br>
* To enable this, run Fineract with the <b>diagnostics</b> profile. * To enable this, run Fineract with the <b>diagnostics</b> profile.
*/ */
@Profile("diagnostics") @Profile(FineractProfiles.DIAGNOSTICS)
@Component @Component
public class DiagnosticsEntityManager implements ApplicationContextAware { public class DiagnosticsEntityManager implements ApplicationContextAware {
@@ -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));
}
}
@@ -19,23 +19,21 @@
package org.apache.fineract.infrastructure.core.filters; package org.apache.fineract.infrastructure.core.filters;
import java.io.IOException;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.service.MDCWrapper; import org.apache.fineract.infrastructure.core.service.MDCWrapper;
import org.apache.fineract.infrastructure.security.utils.LogParameterEscapeUtil; import org.apache.fineract.infrastructure.security.utils.LogParameterEscapeUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@Component
public class CorrelationHeaderFilter extends OncePerRequestFilter { public class CorrelationHeaderFilter extends OncePerRequestFilter {
public static final String CORRELATION_ID_KEY = "correlationId"; public static final String CORRELATION_ID_KEY = "correlationId";
@@ -18,14 +18,14 @@
*/ */
package org.apache.fineract.infrastructure.core.filters; 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.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.mutable.Mutable; 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.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper; import org.springframework.web.util.ContentCachingResponseWrapper;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@Component
public class IdempotencyStoreFilter extends OncePerRequestFilter implements BatchFilter { public class IdempotencyStoreFilter extends OncePerRequestFilter implements BatchFilter {
private final CommandSourceRepository commandSourceRepository; private final CommandSourceRepository commandSourceRepository;
@@ -19,19 +19,17 @@
package org.apache.fineract.infrastructure.core.filters; package org.apache.fineract.infrastructure.core.filters;
import java.io.IOException;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@Component
public class RequestResponseFilter extends OncePerRequestFilter { public class RequestResponseFilter extends OncePerRequestFilter {
@Override @Override
@@ -18,36 +18,31 @@
*/ */
package org.apache.fineract.infrastructure.core.filters; package org.apache.fineract.infrastructure.core.filters;
import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.servlet.FilterChain;
import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.servlet.ServletException;
import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.ext.Provider; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Scope; import java.io.IOException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; 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 * Filter that returns a response with headers that allows for Cross-Origin Requests (CORs) to be performed against the
* platform API. * platform API.
*/ */
@Provider public class ResponseCorsFilter extends OncePerRequestFilter {
@Component
@Scope("singleton")
public class ResponseCorsFilter implements ContainerResponseFilter {
@Override @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", "*"); final String reqHead = request.getHeader("Access-Control-Request-Headers");
// .header("Access-Control-Expose-Headers",
// "Fineract-Platform-TenantId")
response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
final String reqHead = request.getHeaders().getFirst("Access-Control-Request-Headers"); if (StringUtils.hasText(reqHead)) {
response.addHeader("Access-Control-Allow-Headers", reqHead);
if (null != reqHead && StringUtils.hasText(reqHead)) {
response.getHeaders().add("Access-Control-Allow-Headers", reqHead);
} }
filterChain.doFilter(request, response);
} }
} }
@@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration(proxyBeanMethods = false)
@ApplicationPath("/api") @ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig { public class JerseyConfig extends ResourceConfig {
@@ -21,23 +21,21 @@ package org.apache.fineract.infrastructure.instancemode.filter;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter.ExceptionListItem.item; 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.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.HttpMethod;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class FineractInstanceModeApiFilter extends OncePerRequestFilter { public class FineractInstanceModeApiFilter extends OncePerRequestFilter {
@@ -22,6 +22,10 @@ import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeU
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import io.github.resilience4j.core.functions.Either; 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.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
@@ -29,10 +33,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.regex.Pattern; 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 lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; 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.fineract.useradministration.exception.UnAuthenticatedUserException;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchRequestPreprocessor { public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchRequestPreprocessor {
@@ -18,16 +18,15 @@
*/ */
package org.apache.fineract.infrastructure.security.filter; 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.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse; 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.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; 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. * A dummy {@link TwoFactorAuthenticationFilter} filter used when 'twofactor' environment profile is not active.
* *
* This filter adds 'TWOFACTOR_AUTHENTICATED' authority to every authenticated platform user. * 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 class InsecureTwoFactorAuthenticationFilter extends TwoFactorAuthenticationFilter {
public InsecureTwoFactorAuthenticationFilter() { public InsecureTwoFactorAuthenticationFilter() {
@@ -18,13 +18,15 @@
*/ */
package org.apache.fineract.infrastructure.security.filter; 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.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; 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.commons.lang3.time.StopWatch;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; 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.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.apache.fineract.notification.service.UserNotificationService; import org.apache.fineract.notification.service.UserNotificationService;
import org.apache.fineract.useradministration.domain.AppUser; 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.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 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}. * 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. * 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 { public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter {
private static boolean firstRequestProcessed = false; private static boolean firstRequestProcessed = false;
private static final Logger LOG = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class); private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
private final ConfigurationDomainService configurationDomainService;
@Autowired private final CacheWritePlatformService cacheWritePlatformService;
private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; private final UserNotificationService userNotificationService;
private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
@Autowired private final BusinessDateReadPlatformService businessDateReadPlatformService;
private ConfigurationDomainService configurationDomainService;
@Autowired
private CacheWritePlatformService cacheWritePlatformService;
@Autowired
private UserNotificationService userNotificationService;
@Autowired
private BasicAuthTenantDetailsService basicAuthTenantDetailsService;
@Autowired
private BusinessDateReadPlatformService businessDateReadPlatformService;
private final String tenantRequestHeader = "Fineract-Platform-TenantId"; private final String tenantRequestHeader = "Fineract-Platform-TenantId";
private final boolean exceptionIfHeaderMissing = true; private final boolean exceptionIfHeaderMissing = true;
@Setter
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager, 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); super(authenticationManager, authenticationEntryPoint);
this.toApiJsonSerializer = toApiJsonSerializer;
this.configurationDomainService = configurationDomainService;
this.cacheWritePlatformService = cacheWritePlatformService;
this.userNotificationService = userNotificationService;
this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
this.businessDateReadPlatformService = businessDateReadPlatformService;
} }
@Override @Override
@@ -108,48 +104,49 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
// ignore to allow 'preflight' requests from AJAX applications // ignore to allow 'preflight' requests from AJAX applications
// in different origin (domain name) // in different origin (domain name)
} else { } 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 (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.");
}
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 (!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;
}
}
super.doFilterInternal(request, response, filterChain); 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.");
}
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 (!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;
}
}
super.doFilterInternal(request, response, filterChain);
}
} catch (final InvalidTenantIdentifierException e) { } catch (final InvalidTenantIdentifierException e) {
// deal with exception at low level // deal with exception at low level
SecurityContextHolder.getContext().setAuthentication(null); SecurityContextHolder.getContext().setAuthentication(null);
@@ -160,7 +157,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
ThreadLocalContextUtil.reset(); ThreadLocalContextUtil.reset();
task.stop(); task.stop();
final PlatformRequestLog log = PlatformRequestLog.from(task, request); final PlatformRequestLog log = PlatformRequestLog.from(task, request);
LOG.debug("{}", this.toApiJsonSerializer.serialize(log)); TenantAwareBasicAuthenticationFilter.log.debug("{}", this.toApiJsonSerializer.serialize(log));
} }
} }
@@ -18,23 +18,22 @@
*/ */
package org.apache.fineract.infrastructure.security.filter; 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.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; 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.constants.TwoFactorConstants;
import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken; import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
import org.apache.fineract.infrastructure.security.domain.TFAccessToken; import org.apache.fineract.infrastructure.security.domain.TFAccessToken;
import org.apache.fineract.infrastructure.security.service.TwoFactorService; import org.apache.fineract.infrastructure.security.service.TwoFactorService;
import org.apache.fineract.useradministration.domain.AppUser; 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.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.web.filter.GenericFilterBean; 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' * 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. * authority regardless of the value of the 'Fineract-Platform-TFA-Token' header.
*/ */
@Service @RequiredArgsConstructor
@ConditionalOnProperty("fineract.security.2fa.enabled")
public class TwoFactorAuthenticationFilter extends GenericFilterBean { public class TwoFactorAuthenticationFilter extends GenericFilterBean {
private final TwoFactorService twoFactorService; private final TwoFactorService twoFactorService;
@Autowired
public TwoFactorAuthenticationFilter(TwoFactorService twoFactorService) {
this.twoFactorService = twoFactorService;
}
@Override @Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
@@ -21,18 +21,18 @@
--> -->
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" <entity-mappings xmlns="https://jakarta.ee/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm
version="2.0"> https://jakarta.ee/xml/ns/persistence/orm/orm_3_0.xsd" version="3.0">
<persistence-unit-metadata> <persistence-unit-metadata>
<persistence-unit-defaults> <persistence-unit-defaults>
<delimited-identifiers/> <delimited-identifiers/>
<!-- <entity-listeners>--> <entity-listeners>
<!-- <entity-listener--> <entity-listener
<!-- class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />--> class="org.springframework.data.jpa.domain.support.AuditingEntityListener"/>
<!-- </entity-listeners>--> </entity-listeners>
</persistence-unit-defaults> </persistence-unit-defaults>
</persistence-unit-metadata> </persistence-unit-metadata>
+2
View File
@@ -42,6 +42,8 @@ dependencies {
exclude group: 'org.apache.sling' exclude group: 'org.apache.sling'
exclude group: 'com.sun.xml.bind' exclude group: 'com.sun.xml.bind'
} }
testImplementation 'org.apache.groovy:groovy-xml'
testImplementation 'org.apache.groovy:groovy-json'
testImplementation 'org.awaitility:awaitility' testImplementation 'org.awaitility:awaitility'
testCompileOnly 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok'