You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2023/06/06 09:45:32 UTC

[fineract] 15/15: FINERACT-1724: Web security adjustments

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 d87d48366573acb24726d732aa26225d591567fd
Author: Arnold Galovics <ga...@gmail.com>
AuthorDate: Mon Jun 5 21:27:22 2023 +0200

    FINERACT-1724: Web security adjustments
---
 .../core/config/OAuth2SecurityConfig.java          | 59 ++++++++++++----------
 .../infrastructure/core/config/SecurityConfig.java | 53 +++++++++----------
 .../TenantAwareBasicAuthenticationFilter.java      |  1 +
 .../filter/TenantAwareTenantIdentifierFilter.java  |  4 --
 4 files changed, 55 insertions(+), 62 deletions(-)

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 070f91968..7411069de 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
@@ -23,6 +23,7 @@ import static org.apache.fineract.infrastructure.security.vote.SelfServiceUserAu
 import static org.springframework.security.authorization.AuthenticatedAuthorizationManager.fullyAuthenticated;
 import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
 import static org.springframework.security.authorization.AuthorizationManagers.allOf;
+import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
 
 import java.util.Collection;
 import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
@@ -32,6 +33,7 @@ import org.apache.fineract.infrastructure.core.exceptionmapper.OAuth2ExceptionEn
 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.InsecureTwoFactorAuthenticationFilter;
 import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter;
 import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter;
 import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
@@ -40,6 +42,7 @@ 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.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.convert.converter.Converter;
@@ -73,7 +76,7 @@ public class OAuth2SecurityConfig {
     private ServerProperties serverProperties;
 
     @Autowired
-    private TwoFactorService twoFactorService;
+    private FineractProperties fineractProperties;
 
     @Autowired
     private BasicAuthTenantDetailsService basicAuthTenantDetailsService;
@@ -89,45 +92,40 @@ public class OAuth2SecurityConfig {
 
     @Autowired
     private BusinessDateReadPlatformService businessDateReadPlatformService;
+    @Autowired
+    private ApplicationContext applicationContext;
 
     private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
 
-    @Bean
-    public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
-        http //
-                .securityMatcher("/api/**").authorizeHttpRequests((auth) -> {
-                    auth.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/echo").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() //
-                            .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 {
         http //
-                .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
+                .securityMatcher(antMatcher("/api/**")).authorizeHttpRequests((auth) -> {
+                    auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/api/**")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/echo")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/authentication")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/authentication")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/registration")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/registration/user")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() //
+                            .requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() //
+                            .requestMatchers(antMatcher("/api/**"))
+                            .access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"), selfServiceUserAuthManager())); //
+                }).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);
+
+        if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
+            http.addFilterAfter(twoFactorAuthenticationFilter(), BasicAuthenticationFilter.class);
+        } else {
+            http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), BasicAuthenticationFilter.class);
+        }
 
         if (serverProperties.getSsl().isEnabled()) {
-            http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
+            http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/api/**")).requiresSecure());
         }
 
         return http.build();
@@ -139,9 +137,14 @@ public class OAuth2SecurityConfig {
     }
 
     public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
+        TwoFactorService twoFactorService = applicationContext.getBean(TwoFactorService.class);
         return new TwoFactorAuthenticationFilter(twoFactorService);
     }
 
+    public InsecureTwoFactorAuthenticationFilter insecureTwoFactorAuthenticationFilter() {
+        return new InsecureTwoFactorAuthenticationFilter();
+    }
+
     @Bean
     public PasswordEncoder passwordEncoder() {
         return PasswordEncoderFactories.createDelegatingPasswordEncoder();
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 564b96307..4fc460085 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
@@ -22,6 +22,7 @@ package org.apache.fineract.infrastructure.core.config;
 import static org.springframework.security.authorization.AuthenticatedAuthorizationManager.fullyAuthenticated;
 import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
 import static org.springframework.security.authorization.AuthorizationManagers.allOf;
+import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
 
 import org.apache.fineract.commands.domain.CommandSourceRepository;
 import org.apache.fineract.commands.service.CommandSourceService;
@@ -67,7 +68,6 @@ 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.context.SecurityContextHolderFilter;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 
 @Configuration
 @ConditionalOnProperty("fineract.security.basicauth.enabled")
@@ -114,48 +114,41 @@ public class SecurityConfig {
     @Autowired
     private IdempotencyStoreHelper idempotencyStoreHelper;
 
-    @Bean
-    public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
-        http //
-                .securityMatcher("/api/**").authorizeHttpRequests((auth) -> {
-                    auth.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/echo").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() //
-                            .requestMatchers(HttpMethod.PUT, "/api/*/instance-mode").permitAll() //
-                            .requestMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() //
-                            .requestMatchers("/api/*/twofactor").fullyAuthenticated() //
-                            .requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); //
-                }) //
-                .httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint()));
-
-        if (serverProperties.getSsl().isEnabled()) {
-            http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
-        }
-        return http.build();
-    }
-
     @Bean
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http //
+                .securityMatcher(antMatcher("/api/**")).authorizeHttpRequests((auth) -> {
+                    auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/api/**")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/echo")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/authentication")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/authentication")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/registration")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/registration/user")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/instance-mode")).permitAll() //
+                            .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() //
+                            .requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() //
+                            .requestMatchers(antMatcher("/api/**"))
+                            .access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); //
+                }).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())) //
                 .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
                 .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
-                .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class)
-                .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class)
-                .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class)
+                .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) //
+                .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) //
+                .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) //
                 .addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) //
                 .addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) //
                 .addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) //
                 .addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
 
         if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
-            http.addFilterAfter(twoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class);
+            http.addFilterAfter(twoFactorAuthenticationFilter(), ResponseCorsFilter.class);
         } else {
-            http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class);
+            http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), ResponseCorsFilter.class);
         }
 
+        if (serverProperties.getSsl().isEnabled()) {
+            http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/api/**")).requiresSecure());
+        }
         return http.build();
     }
 
@@ -196,7 +189,7 @@ public class SecurityConfig {
         TenantAwareBasicAuthenticationFilter filter = new TenantAwareBasicAuthenticationFilter(authenticationManagerBean(),
                 basicAuthenticationEntryPoint(), toApiJsonSerializer, configurationDomainService, cacheWritePlatformService,
                 userNotificationService, basicAuthTenantDetailsService, businessDateReadPlatformService);
-        filter.setRequestMatcher(AntPathRequestMatcher.antMatcher("/api/**"));
+        filter.setRequestMatcher(antMatcher("/api/**"));
         return filter;
     }
 
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 527715343..99059c16a 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
@@ -106,6 +106,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
             if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
                 // ignore to allow 'preflight' requests from AJAX applications
                 // in different origin (domain name)
+                filterChain.doFilter(request, response);
             } else {
                 if (requestMatcher.matches(request)) {
                     String tenantIdentifier = request.getHeader(this.tenantRequestHeader);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
index aa09aa266..4ea5301ec 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareTenantIdentifierFilter.java
@@ -42,9 +42,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
 import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException;
 import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Service;
 import org.springframework.web.filter.GenericFilterBean;
 
 /**
@@ -58,8 +56,6 @@ import org.springframework.web.filter.GenericFilterBean;
  *
  * Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active.
  */
-@Service
-@ConditionalOnProperty("fineract.security.oauth.enabled")
 @RequiredArgsConstructor
 @Slf4j
 public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {