From b0657226051a95de9b7448fd457c492f668bf6bf Mon Sep 17 00:00:00 2001 From: Marinov Avgustin Date: Thu, 29 Jun 2023 16:57:30 +0300 Subject: [PATCH] [#1383] Spring Boot 3 Migration / Step 1 1. PagingAndSortingRepository doesn't extend CrudRepository anymore. For all extending that interface repositories CrudRepository super interface shall be now declared (https://spring.io/blog/2022/02/22/announcing-listcrudrepository-friends-for-spring-data-3-0 - ``` The popular PagingAndSortingRepository used to extend from CrudRepository, but it no longer does. This lets you combine it with either CrudRepository or ListCrudRepository or a base interface of your own creation. This means you now have to explicitly extend from a CRUD fragment, even when you already extend from PagingAndSortingRepository. ``` ) 2. org.eclipse.hawkbit.autoconfigure.mgmt.ui -> move in hawkbit-ui (to be ready for removal), anyway - it's a better location for ui related configs 3. extends WebMvcConfigurerAdapter -> implements WebMvcConfigurer 4. remove WebSecurityConfigurerAdapter -> https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#_stop_using_websecurityconfigureradapter, https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter and add @Order to the bean reg!! 5. Use configurers (the other will be deprecated / removed), e.d: http.csrf().disable() -> http.csrf(AbstractHttpConfigurer::disable) 6. configure(final AuthenticationManagerBuilder auth) -> put in httpsecurity config - http.getSharedObject(AuthenticationManagerBuilder.class).... (https://www.baeldung.com/spring-security-authentication-provider) 7. configure(final WebSecurity webSecurity) -> ``` @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().antMatchers("/documentation/**", "/VAADIN/**", "/*.*", "/docs/**"); } ``` (https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter) 8. AuthenticationManager authenticationManagerBean() -> ``` @Bean AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } ``` (https://backendstory.com/spring-security-how-to-replace-websecurityconfigureradapter/) 9. WebMvcAutoConfiguration could be removed - it uses deprectated methods, and sets properties that are same by default - hence - not neeeded (https://github.com/spring-projects/spring-framework/issues/23915#issuecomment-563987147) Signed-off-by: Marinov Avgustin --- CONTRIBUTING.md | 2 - hawkbit-autoconfigure/pom.xml | 6 - .../OidcUserManagementAutoConfiguration.java | 12 +- .../SecurityManagedConfiguration.java | 696 ++++++------------ .../web/WebMvcAutoConfiguration.java | 35 - .../main/resources/META-INF/spring.factories | 2 - hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml | 5 - .../rabbitmq/test/AmqpTestConfiguration.java | 5 +- .../rabbitmq/test/RabbitMqSetupService.java | 15 +- .../repository/jpa/BaseEntityRepository.java | 3 +- .../DistributionSetMetadataRepository.java | 2 + .../jpa/DistributionSetTypeRepository.java | 3 +- .../jpa/SoftwareModuleMetadataRepository.java | 2 + .../jpa/TargetMetadataRepository.java | 2 + .../repository/jpa/TargetTypeRepository.java | 3 +- .../jpa/TenantMetaDataRepository.java | 5 +- .../jpa/AbstractJpaIntegrationTest.java | 3 +- .../AbstractDDiApiIntegrationTest.java | 3 +- .../AbstractManagementApiIntegrationTest.java | 3 +- .../AbstractApiRestDocumentation.java | 3 +- .../org/eclipse/hawkbit/app/CorsTest.java | 12 +- hawkbit-ui/pom.xml | 10 + .../MgmtUiAutoConfiguration.java | 2 +- .../ui/autoconfigure}/RedirectController.java | 2 +- .../UISecurityConfigurationAdapter.java | 306 ++++++++ .../main/resources/META-INF/spring.factories | 3 + pom.xml | 18 +- 27 files changed, 581 insertions(+), 582 deletions(-) delete mode 100644 hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/web/WebMvcAutoConfiguration.java rename {hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui => hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure}/MgmtUiAutoConfiguration.java (98%) rename {hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui => hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure}/RedirectController.java (94%) create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/UISecurityConfigurationAdapter.java create mode 100644 hawkbit-ui/src/main/resources/META-INF/spring.factories diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32be36d87a..e69fd0d40b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,8 +35,6 @@ So we kindly ask contributors: * use [Guava](https://github.com/google/guava) if feasible * use [Apache commons lang](https://commons.apache.org/proper/commons-lang/) if feasible -Note that the guava project for instance often documents where they think that JDK is having a similar functionality (e.g. their thoughts on [Throwables.propagate](https://github.com/google/guava/wiki/Why-we-deprecated-Throwables.propagate)). - Examples: * Prefer `Arrays.asList(...)` from JDK over Guava's `Lists.newArrayList(...)` diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index a2c0ede903..3fc08c1072 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -31,12 +31,6 @@ ${project.version} true - - org.eclipse.hawkbit - hawkbit-ui - ${project.version} - true - org.eclipse.hawkbit hawkbit-repository-jpa diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java index bf3c643222..463ba48fe3 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java @@ -53,10 +53,11 @@ import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtException; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; @@ -261,8 +262,11 @@ class JwtAuthoritiesExtractor { Set extract(final ClientRegistration clientRegistration, final String tokenValue) { try { // Token is already verified by spring security - final JwtDecoder jwtDecoder = new NimbusJwtDecoderJwkSupport( - clientRegistration.getProviderDetails().getJwkSetUri()); + final NimbusJwtDecoder jwtDecoder = + NimbusJwtDecoder + .withJwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri()) + .jwsAlgorithm(SignatureAlgorithm.from(JwsAlgorithms.RS256)) + .build(); final Jwt token = jwtDecoder.decode(tokenValue); return extract(clientRegistration.getClientId(), token.getClaims()); diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index 702289d2f2..5706d45498 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -9,7 +9,6 @@ package org.eclipse.hawkbit.autoconfigure.security; import java.io.IOException; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -20,14 +19,12 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; import org.eclipse.hawkbit.cache.DownloadIdCache; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; -import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration; @@ -46,7 +43,6 @@ import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.ui.MgmtUiConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -54,62 +50,41 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; -import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.firewall.FirewalledRequest; -import org.springframework.security.web.firewall.HttpFirewall; -import org.springframework.security.web.firewall.StrictHttpFirewall; -import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.session.SessionManagementFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsConfiguration; -import org.vaadin.spring.http.HttpService; -import org.vaadin.spring.security.annotation.EnableVaadinSharedSecurity; -import org.vaadin.spring.security.config.VaadinSharedSecurityConfiguration; -import org.vaadin.spring.security.shared.VaadinAuthenticationSuccessHandler; -import org.vaadin.spring.security.shared.VaadinUrlAuthenticationSuccessHandler; -import org.vaadin.spring.security.web.VaadinRedirectStrategy; +import org.springframework.web.cors.CorsConfigurationSource; /** * All configurations related to HawkBit's authentication and authorization @@ -155,18 +130,18 @@ private UserAuthenticationFilterBasicAuth(final AuthenticationManager authentica * {@link WebSecurityConfigurer} for the hawkBit server DDI interface. */ @Configuration - @Order(300) + @EnableWebSecurity @ConditionalOnClass(DdiApiConfiguration.class) - static class ControllerSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { + static class ControllerSecurityConfigurationAdapter { - private static final String[] DDI_ANT_MATCHERS = { DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}", + private static final String[] DDI_ANT_MATCHERS = { + DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}", DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/confirmationBase/**", DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/deploymentBase/**", DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/installedBase/**", DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/cancelAction/**", DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/configData", - DdiRestConstants.BASE_V1_REQUEST_MAPPING - + "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts" }; + DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts" }; private final ControllerManagement controllerManagement; private final TenantConfigurationManagement tenantConfigurationManagement; @@ -200,75 +175,79 @@ static class ControllerSecurityConfigurationAdapter extends WebSecurityConfigure */ @Bean @ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true) - public FilterRegistrationBean dosDDiFilter(final HawkbitSecurityProperties securityProperties) { - - final FilterRegistrationBean filterRegBean = dosFilter(Arrays.asList(DDI_ANT_MATCHERS), - securityProperties.getDos().getFilter(), securityProperties.getClients()); + public FilterRegistrationBean dosFilterDDI(final HawkbitSecurityProperties securityProperties) { + final FilterRegistrationBean filterRegBean = + dosFilter(List.of(DDI_ANT_MATCHERS), + securityProperties.getDos().getFilter(), securityProperties.getClients()); filterRegBean.setOrder(DOS_FILTER_ORDER); filterRegBean.setName("dosDDiFilter"); return filterRegBean; } - @Override - protected void configure(final HttpSecurity http) throws Exception { - - final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); - - final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( - ddiSecurityConfiguration.getRp().getCnHeader(), - ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, - tenantAware, systemSecurityContext); - securityHeaderFilter.setAuthenticationManager(authenticationManager()); - securityHeaderFilter.setCheckForPrincipalChanges(true); - securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( - tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); - securityTokenFilter.setAuthenticationManager(authenticationManager()); - securityTokenFilter.setCheckForPrincipalChanges(true); - securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - tenantConfigurationManagement, tenantAware, systemSecurityContext); - gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager()); - gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); - gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + @Bean + @Order(300) + protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception { + final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration); - HttpSecurity httpSec = http.csrf().disable(); + http + .requestMatchers(requestMatchers -> requestMatchers.antMatchers(DDI_ANT_MATCHERS)) + .csrf(AbstractHttpConfigurer::disable); if (securityProperties.isRequireSsl()) { - httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and(); + http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); } + final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) { - LOG.info( - "******************\n** Anonymous controller security enabled, should only be used for developing purposes **\n******************"); + """ + ****************** + ** Anonymous controller security enabled, should only be used for developing purposes ** + ******************"""); final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter( "controllerAnonymousFilter", "anonymous", - Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS))); + List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS))); anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - httpSec.requestMatchers().antMatchers(DDI_ANT_MATCHERS).and().securityContext().disable().anonymous() - .authenticationFilter(anonymousFilter); + http + .securityContext(AbstractHttpConfigurer::disable) + .anonymous(configurer -> configurer.authenticationFilter(anonymousFilter)); } else { - - httpSec.addFilter(securityHeaderFilter).addFilter(securityTokenFilter) - .addFilter(gatewaySecurityTokenFilter).requestMatchers().antMatchers(DDI_ANT_MATCHERS).and() - .anonymous().disable().authorizeRequests().anyRequest().authenticated().and() - .exceptionHandling() - .authenticationEntryPoint((request, response, authException) -> response - .setStatus(HttpStatus.UNAUTHORIZED.value())) - .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( + ddiSecurityConfiguration.getRp().getCnHeader(), + ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, + tenantAware, systemSecurityContext); + securityHeaderFilter.setAuthenticationManager(authenticationManager); + securityHeaderFilter.setCheckForPrincipalChanges(true); + securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( + tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); + securityTokenFilter.setAuthenticationManager(authenticationManager); + securityTokenFilter.setCheckForPrincipalChanges(true); + securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( + tenantConfigurationManagement, tenantAware, systemSecurityContext); + gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager); + gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); + gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + http + .authorizeHttpRequests(amrmRegistry -> + amrmRegistry.anyRequest().authenticated()) + .anonymous(AbstractHttpConfigurer::disable) + .addFilter(securityHeaderFilter) + .addFilter(securityTokenFilter) + .addFilter(gatewaySecurityTokenFilter) + .exceptionHandling(configurer -> configurer.authenticationEntryPoint( + (request, response, authException) -> + response.setStatus(HttpStatus.UNAUTHORIZED.value()))) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); } - } - - @Override - protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( - ddiSecurityConfiguration.getRp().getTrustedIPs())); + return http.build(); } } @@ -277,9 +256,8 @@ protected void configure(final AuthenticationManagerBuilder auth) throws Excepti * interface. */ @Configuration - @Order(301) @ConditionalOnClass(DdiApiConfiguration.class) - static class ControllerDownloadSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { + static class ControllerDownloadSecurityConfigurationAdapter { private static final String DDI_DL_ANT_MATCHER = DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/*"; @@ -316,9 +294,8 @@ static class ControllerDownloadSecurityConfigurationAdapter extends WebSecurityC */ @Bean @ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true) - public FilterRegistrationBean dosDDiDlFilter(final HawkbitSecurityProperties securityProperties) { - - final FilterRegistrationBean filterRegBean = dosFilter(Arrays.asList(DDI_DL_ANT_MATCHER), + public FilterRegistrationBean dosFilterDDIDL(final HawkbitSecurityProperties securityProperties) { + final FilterRegistrationBean filterRegBean = dosFilter(List.of(DDI_DL_ANT_MATCHER), securityProperties.getDos().getFilter(), securityProperties.getClients()); filterRegBean.setOrder(DOS_FILTER_ORDER); filterRegBean.setName("dosDDiDlFilter"); @@ -326,71 +303,75 @@ public FilterRegistrationBean dosDDiDlFilter(final HawkbitSecurityPro return filterRegBean; } - @Override - protected void configure(final HttpSecurity http) throws Exception { - - final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); + @Bean + @Order(301) + protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception { + final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration); - final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( - ddiSecurityConfiguration.getRp().getCnHeader(), - ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, - tenantAware, systemSecurityContext); - securityHeaderFilter.setAuthenticationManager(authenticationManager()); - securityHeaderFilter.setCheckForPrincipalChanges(true); - securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( - tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); - securityTokenFilter.setAuthenticationManager(authenticationManager()); - securityTokenFilter.setCheckForPrincipalChanges(true); - securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - tenantConfigurationManagement, tenantAware, systemSecurityContext); - gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager()); - gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); - gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticateAnonymousDownloadFilter controllerAnonymousDownloadFilter = new HttpControllerPreAuthenticateAnonymousDownloadFilter( - tenantConfigurationManagement, tenantAware, systemSecurityContext); - controllerAnonymousDownloadFilter.setAuthenticationManager(authenticationManager()); - controllerAnonymousDownloadFilter.setCheckForPrincipalChanges(true); - controllerAnonymousDownloadFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - HttpSecurity httpSec = http.csrf().disable(); + http + .requestMatcher(new AntPathRequestMatcher(DDI_DL_ANT_MATCHER)) + .csrf(AbstractHttpConfigurer::disable); if (securityProperties.isRequireSsl()) { - httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and(); + http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); } - if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) { + final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); + if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) { LOG.info( - "******************\n** Anonymous controller security enabled, should only be used for developing purposes **\n******************"); + """ + ****************** + ** Anonymous controller security enabled, should only be used for developing purposes ** + ******************"""); final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter( "controllerAnonymousFilter", "anonymous", - Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS))); + List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS))); anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - httpSec.requestMatchers().antMatchers(DDI_DL_ANT_MATCHER).and().securityContext().disable().anonymous() - .authenticationFilter(anonymousFilter); + http + .securityContext(AbstractHttpConfigurer::disable) + .anonymous(configurer -> configurer.authenticationFilter(anonymousFilter)); } else { - - httpSec.addFilter(securityHeaderFilter).addFilter(securityTokenFilter) - .addFilter(gatewaySecurityTokenFilter).addFilter(controllerAnonymousDownloadFilter) - .requestMatchers().antMatchers(DDI_DL_ANT_MATCHER).and().anonymous().disable() - .authorizeRequests().anyRequest().authenticated().and().exceptionHandling() - .authenticationEntryPoint((request, response, authException) -> response - .setStatus(HttpStatus.UNAUTHORIZED.value())) - .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( + ddiSecurityConfiguration.getRp().getCnHeader(), + ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, + tenantAware, systemSecurityContext); + securityHeaderFilter.setAuthenticationManager(authenticationManager); + securityHeaderFilter.setCheckForPrincipalChanges(true); + securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( + tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); + securityTokenFilter.setAuthenticationManager(authenticationManager); + securityTokenFilter.setCheckForPrincipalChanges(true); + securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( + tenantConfigurationManagement, tenantAware, systemSecurityContext); + gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager); + gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); + gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + final HttpControllerPreAuthenticateAnonymousDownloadFilter controllerAnonymousDownloadFilter = new HttpControllerPreAuthenticateAnonymousDownloadFilter( + tenantConfigurationManagement, tenantAware, systemSecurityContext); + controllerAnonymousDownloadFilter.setAuthenticationManager(authenticationManager); + controllerAnonymousDownloadFilter.setCheckForPrincipalChanges(true); + controllerAnonymousDownloadFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + http + .authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated()) + .anonymous(AbstractHttpConfigurer::disable) + .addFilter(securityHeaderFilter) + .addFilter(securityTokenFilter) + .addFilter(gatewaySecurityTokenFilter) + .addFilter(controllerAnonymousDownloadFilter) + .exceptionHandling(configurer -> configurer.authenticationEntryPoint( + (request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); } - } - - @Override - protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( - ddiSecurityConfiguration.getRp().getTrustedIPs())); + return http.build(); } } @@ -407,10 +388,9 @@ protected void configure(final AuthenticationManagerBuilder auth) throws Excepti @Bean @ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true) public FilterRegistrationBean dosSystemFilter(final HawkbitSecurityProperties securityProperties) { - final FilterRegistrationBean filterRegBean = dosFilter(Collections.emptyList(), securityProperties.getDos().getFilter(), securityProperties.getClients()); - filterRegBean.setUrlPatterns(Arrays.asList("/system/*")); + filterRegBean.setUrlPatterns(List.of("/system/*")); filterRegBean.setOrder(DOS_FILTER_ORDER); filterRegBean.setName("dosSystemFilter"); @@ -420,7 +400,6 @@ public FilterRegistrationBean dosSystemFilter(final HawkbitSecurityPr private static FilterRegistrationBean dosFilter(final Collection includeAntPaths, final HawkbitSecurityProperties.Dos.Filter filterProperties, final HawkbitSecurityProperties.Clients clientProperties) { - final FilterRegistrationBean filterRegBean = new FilterRegistrationBean<>(); filterRegBean.setFilter(new DosFilter(includeAntPaths, filterProperties.getMaxRead(), @@ -435,36 +414,30 @@ private static FilterRegistrationBean dosFilter(final Collection armrRepository.anyRequest().authenticated()) + .csrf(AbstractHttpConfigurer::disable) + .anonymous(AbstractHttpConfigurer::disable) + .addFilterBefore(downloadIdAuthenticationFilter, FilterSecurityInterceptor.class) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - http.antMatcher("/**/downloadId/**").addFilterBefore(downloadIdAuthenticationFilter, - FilterSecurityInterceptor.class); - http.authorizeRequests().anyRequest().authenticated().and().sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } - - @Override - protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( - ddiSecurityConfiguration.getRp().getTrustedIPs())); + return http.build(); } } @@ -472,72 +445,79 @@ protected void configure(final AuthenticationManagerBuilder auth) throws Excepti * Security configuration for the REST management API. */ @Configuration - @Order(350) @EnableWebSecurity @ConditionalOnClass(MgmtApiConfiguration.class) - public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { - - @Autowired - @Lazy - private UserAuthenticationFilter userAuthenticationFilter; - - @Autowired(required = false) - private OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter; - - @Autowired(required = false) - private InMemoryClientRegistrationRepository clientRegistrationRepository; + public static class RestSecurityConfigurationAdapter { - @Autowired - private SystemManagement systemManagement; - - @Autowired - private HawkbitSecurityProperties securityProperties; + private final HawkbitSecurityProperties securityProperties; - @Autowired - private SystemSecurityContext systemSecurityContext; + public RestSecurityConfigurationAdapter(final HawkbitSecurityProperties securityProperties) { + this.securityProperties = securityProperties; + } /** * Filter to protect the hawkBit server Management interface against to * many requests. * - * @param securityProperties - * for filter configuration - * * @return the spring filter registration bean for registering a denial * of service protection filter in the filter chain */ @Bean @ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true) - public FilterRegistrationBean dosMgmtFilter(final HawkbitSecurityProperties securityProperties) { - + public FilterRegistrationBean dosFilterREST() { final FilterRegistrationBean filterRegBean = dosFilter(null, securityProperties.getDos().getFilter(), securityProperties.getClients()); - filterRegBean.setUrlPatterns(Arrays.asList("/rest/*", "/api/*")); + filterRegBean.setUrlPatterns(List.of("/rest/*", "/api/*")); filterRegBean.setOrder(DOS_FILTER_ORDER); filterRegBean.setName("dosMgmtFilter"); return filterRegBean; } - @Override - protected void configure(final HttpSecurity http) throws Exception { - - HttpSecurity httpSec = http.requestMatchers().antMatchers("/rest/**", "/system/admin/**").and().csrf() - .disable(); + @Bean + @Order(350) + protected SecurityFilterChain filterChainREST( + final HttpSecurity http, + @Lazy + final UserAuthenticationFilter userAuthenticationFilter, + @Autowired(required = false) + final OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter, + @Autowired(required = false) + final InMemoryClientRegistrationRepository clientRegistrationRepository, + final SystemManagement systemManagement, + final SystemSecurityContext systemSecurityContext) + throws Exception { + http + .requestMatchers(requestMatchers -> requestMatchers.antMatchers("/rest/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")) + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(amrmRegistry -> + amrmRegistry + .antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") + .hasAnyAuthority(SpPermission.SYSTEM_ADMIN) + .anyRequest() + .authenticated()) + .addFilterAfter( + // Servlet filter to create metadata after successful authentication over RESTful. + (request, response, chain) -> { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + systemSecurityContext.runAsSystem(systemManagement::getTenantMetadata); + } + chain.doFilter(request, response); + }, + SessionManagementFilter.class) + .anonymous(AbstractHttpConfigurer::disable) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); if (securityProperties.getCors().isEnabled()) { - httpSec = httpSec.cors().configurationSource(reuest -> corsConfiguration()).and(); + http.cors(configurer -> configurer.configurationSource(corsConfigurationSource())); } if (securityProperties.isRequireSsl()) { - httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and(); + http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); } - httpSec.authorizeRequests().antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") - .hasAnyAuthority(SpPermission.SYSTEM_ADMIN).anyRequest().authenticated(); - if (oidcBearerTokenAuthenticationFilter != null) { - // Only get the first client registration. Testing against every // client could increase the // attack vector @@ -547,16 +527,16 @@ protected void configure(final HttpSecurity http) throws Exception { : null; Assert.notNull(clientRegistration, "There must be a valid client registration"); - httpSec.oauth2ResourceServer().jwt().jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri()); + http.oauth2ResourceServer(configurer -> configurer.jwt().jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri())); oidcBearerTokenAuthenticationFilter.setClientRegistration(clientRegistration); - httpSec.addFilterAfter(oidcBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class); + http.addFilterAfter(oidcBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class); } else { final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint(); basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm()); - httpSec.addFilterBefore(new Filter() { + http.addFilterBefore(new Filter() { @Override public void init(final FilterConfig filterConfig) throws ServletException { userAuthenticationFilter.init(filterConfig); @@ -573,20 +553,22 @@ public void destroy() { userAuthenticationFilter.destroy(); } }, RequestHeaderAuthenticationFilter.class); - httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint); + http + .httpBasic(Customizer.withDefaults()) + .exceptionHandling(configurer -> configurer.authenticationEntryPoint(basicAuthEntryPoint)); } - httpSec.addFilterAfter( - new AuthenticationSuccessTenantMetadataCreationFilter(systemManagement, systemSecurityContext), - SessionManagementFilter.class); - - httpSec.anonymous().disable(); - httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + return http.build(); } @Bean - @ConditionalOnProperty(prefix = "hawkbit.server.security.cors", name = "enabled", matchIfMissing = false) - CorsConfiguration corsConfiguration() { + @ConditionalOnProperty(prefix = "hawkbit.server.security.cors", name = "enabled") + CorsConfigurationSource corsConfigurationSource() { + final CorsConfiguration configuration = corsConfiguration(); + return request -> configuration; + } + + private CorsConfiguration corsConfiguration() { final CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(securityProperties.getCors().getAllowedOrigins()); @@ -599,281 +581,15 @@ CorsConfiguration corsConfiguration() { } } - /** - * {@link WebSecurityConfigurer} for external (management) access. - */ - @Configuration - @Order(400) - @EnableWebSecurity - @EnableVaadinSharedSecurity - @ConditionalOnClass(MgmtUiConfiguration.class) - public static class UISecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { - - @Autowired - private HawkbitSecurityProperties hawkbitSecurityProperties; - - @Autowired(required = false) - private OAuth2UserService oidcUserService; - - @Autowired(required = false) - private AuthenticationSuccessHandler authenticationSuccessHandler; - - @Autowired - private LogoutHandler logoutHandler; - - @Autowired - private LogoutSuccessHandler logoutSuccessHandler; - - /** - * Filter to protect the hawkBit management UI against to many requests. - * - * @param securityProperties - * for filter configuration - * - * @return the spring filter registration bean for registering a denial - * of service protection filter in the filter chain - */ - @Bean - @ConditionalOnProperty(prefix = "hawkbit.server.security.dos.ui-filter", name = "enabled", matchIfMissing = true) - public FilterRegistrationBean dosMgmtUiFilter(final HawkbitSecurityProperties securityProperties) { - - final FilterRegistrationBean filterRegBean = dosFilter(null, - securityProperties.getDos().getUiFilter(), securityProperties.getClients()); - // All URLs that can be called anonymous - filterRegBean.setUrlPatterns(Arrays.asList("/UI/login", "/UI/login/*", "/UI/logout", "/UI/logout/*")); - filterRegBean.setOrder(DOS_FILTER_ORDER); - filterRegBean.setName("dosMgmtUiFilter"); - - return filterRegBean; - } - - @Override - @Bean(name = VaadinSharedSecurityConfiguration.AUTHENTICATION_MANAGER_BEAN) - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - /** - * Overwriting VaadinAuthenticationSuccessHandler of default - * VaadinSharedSecurityConfiguration - * - * @return the vaadin success authentication handler - */ - @Primary - @Bean(name = VaadinSharedSecurityConfiguration.VAADIN_AUTHENTICATION_SUCCESS_HANDLER_BEAN) - public VaadinAuthenticationSuccessHandler redirectSaveHandler(final HttpService httpService, - final VaadinRedirectStrategy redirectStrategy) { - final VaadinUrlAuthenticationSuccessHandler handler = new TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler( - httpService, redirectStrategy, "/UI/"); - handler.setTargetUrlParameter("r"); - - return handler; - } - - /** - * Listener to redirect to login page after session timeout. Close the - * vaadin session, because it's is not possible to redirect in - * atmosphere. - * - * @return the servlet listener. - */ - @Bean - public ServletListenerRegistrationBean httpSessionEventPublisher() { - return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher()); - } - - @Override - protected void configure(final HttpSecurity http) throws Exception { - - final boolean enableOidc = oidcUserService != null && authenticationSuccessHandler != null; - - // workaround regex: we need to exclude the URL /UI/HEARTBEAT here - // because we bound the vaadin application to /UI and not to root, - // described in vaadin-forum: - // https://vaadin.com/forum#!/thread/3200565. - HttpSecurity httpSec; - if (enableOidc) { - httpSec = http.requestMatchers().antMatchers("/**/UI/**", "/**/oauth2/**").and(); - } else { - httpSec = http.antMatcher("/**/UI/**"); - } - // disable as CSRF is handled by Vaadin - httpSec.csrf().disable(); - // allow same origin X-Frame-Options for correct file download under - // Safari - httpSec.headers().frameOptions().sameOrigin(); - - if (hawkbitSecurityProperties.isRequireSsl()) { - httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and(); - } else { - - LOG.info( - "\"******************\\n** Requires HTTPS Security has been disabled for UI, should only be used for developing purposes **\\n******************\""); - } - - if (!StringUtils.isEmpty(hawkbitSecurityProperties.getContentSecurityPolicy())) { - httpSec.headers().contentSecurityPolicy(hawkbitSecurityProperties.getContentSecurityPolicy()); - } - - // UI - httpSec.authorizeRequests().antMatchers("/UI/login/**", "/UI/UIDL/**").permitAll().anyRequest() - .authenticated(); - - if (enableOidc) { - // OIDC - httpSec.oauth2Login().userInfoEndpoint().oidcUserService(oidcUserService).and() - .successHandler(authenticationSuccessHandler).and().oauth2Client(); - } else { - // UI login / Basic auth - httpSec.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/UI/login")); - } - - // UI logout - httpSec.logout().logoutUrl("/UI/logout*").addLogoutHandler(logoutHandler) - .logoutSuccessHandler(logoutSuccessHandler); - } - - /** - * HttpFirewall which enables to define a list of allowed host names. - * - * @return the http firewall. - */ - @Bean - public HttpFirewall httpFirewall() { - final List allowedHostNames = hawkbitSecurityProperties.getAllowedHostNames(); - final IgnorePathsStrictHttpFirewall firewall = new IgnorePathsStrictHttpFirewall( - hawkbitSecurityProperties.getHttpFirewallIgnoredPaths()); - - if (!CollectionUtils.isEmpty(allowedHostNames)) { - firewall.setAllowedHostnames(hostName -> { - LOG.debug("Firewall check host: {}, allowed: {}", hostName, allowedHostNames.contains(hostName)); - return allowedHostNames.contains(hostName); - }); - } - return firewall; - } - - private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall { - - private final Collection pathsToIgnore; - - public IgnorePathsStrictHttpFirewall(final Collection pathsToIgnore) { - super(); - this.pathsToIgnore = pathsToIgnore; - } - - @Override - public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) { - if (pathsToIgnore != null && pathsToIgnore.contains(request.getRequestURI())) { - return new FirewalledRequest(request) { - @Override - public void reset() { - // nothing to do - } - }; - } - return super.getFirewalledRequest(request); - } - } - - @Override - public void configure(final WebSecurity webSecurity) throws Exception { - // No security for static content - webSecurity.ignoring().antMatchers("/documentation/**", "/VAADIN/**", "/*.*", "/docs/**"); - } - - /** - * Configuration that defines the {@link AccessDecisionManager} bean for - * UI method security used by the Vaadin Servlet. Notice: we can not use - * the top-level method security configuration because - * {@link AdviceMode.ASPECTJ} is not supported. - */ - @Configuration - @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true) - @ConditionalOnClass(MgmtUiConfiguration.class) - static class UIMethodSecurity extends GlobalMethodSecurityConfiguration { - - @Bean(name = VaadinSharedSecurityConfiguration.ACCESS_DECISION_MANAGER_BEAN) - @Override - protected AccessDecisionManager accessDecisionManager() { - return super.accessDecisionManager(); - } - } - } -} - -/** - * After a successful login on the UI we need to ensure to create the tenant - * meta data within SP. - */ -class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends VaadinUrlAuthenticationSuccessHandler { - - @Autowired - private SystemManagement systemManagement; - - @Autowired - private SystemSecurityContext systemSecurityContext; - - public TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler(final HttpService http, - final VaadinRedirectStrategy redirectStrategy, final String defaultTargetUrl) { - super(http, redirectStrategy, defaultTargetUrl); + private static AuthenticationManager setAuthenticationManager(final HttpSecurity http, final DdiSecurityProperties ddiSecurityConfiguration) throws Exception { + // configure authentication manager + final AuthenticationManager authenticationManager = + http + .getSharedObject(AuthenticationManagerBuilder.class) + .authenticationProvider( + new PreAuthTokenSourceTrustAuthenticationProvider(ddiSecurityConfiguration.getRp().getTrustedIPs())) + .build(); + http.authenticationManager(authenticationManager); + return authenticationManager; } - - @Override - public void onAuthenticationSuccess(final Authentication authentication) throws Exception { - systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, getTenantFrom(authentication)); - - super.onAuthenticationSuccess(authentication); - } - - private static String getTenantFrom(final Authentication authentication) { - final Object details = authentication.getDetails(); - if (details instanceof TenantAwareAuthenticationDetails) { - return ((TenantAwareAuthenticationDetails) details).getTenant(); - } - - throw new InsufficientAuthenticationException("Authentication details/tenant info are not specified!"); - } -} - -/** - * Servletfilter to create metadata after successful authentication over - * RESTful. - */ -class AuthenticationSuccessTenantMetadataCreationFilter implements Filter { - - private final SystemManagement systemManagement; - private final SystemSecurityContext systemSecurityContext; - - AuthenticationSuccessTenantMetadataCreationFilter(final SystemManagement systemManagement, - final SystemSecurityContext systemSecurityContext) { - this.systemManagement = systemManagement; - this.systemSecurityContext = systemSecurityContext; - } - - @Override - public void init(final FilterConfig filterConfig) throws ServletException { - // not needed - } - - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) - throws IOException, ServletException { - lazyCreateTenantMetadata(); - chain.doFilter(request, response); - - } - - private void lazyCreateTenantMetadata() { - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.isAuthenticated()) { - systemSecurityContext.runAsSystem(systemManagement::getTenantMetadata); - } - } - - @Override - public void destroy() { - // not needed - } - } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/web/WebMvcAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/web/WebMvcAutoConfiguration.java deleted file mode 100644 index e0a770137e..0000000000 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/web/WebMvcAutoConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.autoconfigure.web; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -/** - * A configuration bean which disables the {@code useSuffixPatternMatch} feature - * from Spring because it will truncate the dot in a REST URL which leads to - * problem in case a controllerId contains dots and is a path parameter or - * filename ending. - */ -@Configuration -public class WebMvcAutoConfiguration extends WebMvcConfigurerAdapter { - - @Override - public void configurePathMatch(final PathMatchConfigurer configurer) { - configurer.setUseSuffixPatternMatch(false); - configurer.setUseRegisteredSuffixPatternMatch(false); - } - - @Override - public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) { - configurer.favorPathExtension(false); - } -} diff --git a/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories b/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories index 5716eb54a7..501071626a 100644 --- a/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories @@ -4,7 +4,6 @@ org.eclipse.hawkbit.autoconfigure.cache.CacheAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.cache.DownloadIdCacheAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.ddi.DDiApiAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.dmf.amqp.DmfApiAutoConfiguration,\ -org.eclipse.hawkbit.autoconfigure.mgmt.ui.MgmtUiAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.repository.event.EventPublisherAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.repository.ArtifactFilesystemAutoConfiguration,\ @@ -14,5 +13,4 @@ org.eclipse.hawkbit.autoconfigure.scheduling.ExecutorAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.security.SecurityAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.security.InMemoryUserManagementAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.security.OidcUserManagementAutoConfiguration,\ -org.eclipse.hawkbit.autoconfigure.web.WebMvcAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.PropertyHostnameResolverAutoConfiguration diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml index 79d3006c2c..9f4697a83b 100644 --- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml @@ -61,11 +61,6 @@ spring-boot-starter-web compile - - org.apache.httpcomponents - httpclient - compile - org.springframework.amqp spring-rabbit-test diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java index 5dd491a1fc..0a21198340 100644 --- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java @@ -29,8 +29,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; -import com.google.common.base.Throwables; - /** * */ @@ -62,13 +60,14 @@ ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } + @SuppressWarnings("java:S112") @Bean HostnameResolver hostnameResolver(final HawkbitServerProperties serverProperties) { return () -> { try { return new URL(serverProperties.getUrl()); } catch (final MalformedURLException e) { - throw Throwables.propagate(e); + throw new RuntimeException(e); } }; } diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/RabbitMqSetupService.java b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/RabbitMqSetupService.java index 530a153ec6..43c57adbbe 100644 --- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/RabbitMqSetupService.java +++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/RabbitMqSetupService.java @@ -10,6 +10,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; +import java.net.URL; import java.util.UUID; import javax.annotation.PreDestroy; @@ -19,7 +20,6 @@ import org.springframework.amqp.rabbit.junit.BrokerRunningSupport; import org.springframework.util.StringUtils; -import com.google.common.base.Throwables; import com.rabbitmq.http.client.Client; import com.rabbitmq.http.client.domain.UserPermissions; @@ -39,9 +39,9 @@ public class RabbitMqSetupService { private final String hostname; - private String username; + private final String username; - private String password; + private final String password; public RabbitMqSetupService() { @@ -52,12 +52,13 @@ public RabbitMqSetupService() { password = brokerSupport.getPassword(); } + @SuppressWarnings("java:S112") private synchronized Client getRabbitmqHttpClient() { if (rabbitmqHttpClient == null) { try { - rabbitmqHttpClient = new Client(getHttpApiUrl(), getUsername(), getPassword()); - } catch (MalformedURLException | URISyntaxException e) { - throw Throwables.propagate(e); + rabbitmqHttpClient = new Client(new URL(getHttpApiUrl()), getUsername(), getPassword()); + } catch (final MalformedURLException | URISyntaxException e) { + throw new RuntimeException(e); } } return rabbitmqHttpClient; @@ -77,7 +78,7 @@ public ConnectionFactory newVirtualHostWithConnectionFactory() { @PreDestroy public void deleteVirtualHost() { - if (StringUtils.isEmpty(virtualHost)) { + if (StringUtils.hasText(virtualHost)) { return; } getRabbitmqHttpClient().deleteVhost(virtualHost); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java index 1e79ce588f..2c547f14d8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java @@ -15,6 +15,7 @@ import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.transaction.annotation.Transactional; @@ -30,7 +31,7 @@ @NoRepositoryBean @Transactional(readOnly = true) public interface BaseEntityRepository - extends PagingAndSortingRepository, NoCountSliceRepository { + extends PagingAndSortingRepository, CrudRepository, NoCountSliceRepository { /** * Retrieves an {@link BaseEntity} by its id. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java index ab19ca0670..545e5de933 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java @@ -12,6 +12,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +23,7 @@ @Transactional(readOnly = true) public interface DistributionSetMetadataRepository extends PagingAndSortingRepository, + CrudRepository, JpaSpecificationExecutor { /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java index 65ca4e3d81..f80c86c217 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java @@ -27,7 +27,8 @@ import org.springframework.transaction.annotation.Transactional; /** - * {@link PagingAndSortingRepository} for {@link DistributionSetType}. + * {@link PagingAndSortingRepository} and {@link org.springframework.data.repository.CrudRepository} for + * {@link DistributionSetType}. * */ @Transactional(readOnly = true) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java index 022023a3ba..fa3cec4d31 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java @@ -17,6 +17,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +29,7 @@ @Transactional(readOnly = true) public interface SoftwareModuleMetadataRepository extends PagingAndSortingRepository, + CrudRepository, JpaSpecificationExecutor { /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java index baa8d22b0e..588092a2bf 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java @@ -12,6 +12,7 @@ import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey; import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +23,7 @@ @Transactional(readOnly = true) public interface TargetMetadataRepository extends PagingAndSortingRepository, + CrudRepository, JpaSpecificationExecutor { /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java index 2aaf90eb09..c815efce3b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java @@ -30,7 +30,8 @@ import org.springframework.transaction.annotation.Transactional; /** - * {@link PagingAndSortingRepository} for {@link JpaTargetType}. + * {@link PagingAndSortingRepository} and {@link org.springframework.data.repository.CrudRepository} for + * {@link JpaTargetType}. * */ @Transactional(readOnly = true) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java index 6bbb87fdea..8e8a9ee07c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java @@ -14,6 +14,7 @@ import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -23,7 +24,9 @@ * */ @Transactional(readOnly = true) -public interface TenantMetaDataRepository extends PagingAndSortingRepository { +public interface TenantMetaDataRepository + extends PagingAndSortingRepository, + CrudRepository { /** * Search {@link TenantMetaData} by tenant name. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index c7957f8e94..8673035e5d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -42,7 +42,8 @@ import com.google.common.collect.Lists; -@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, TestConfiguration.class, +@ContextConfiguration(classes = { + RepositoryApplicationConfiguration.class, TestConfiguration.class, TestSupportBinderAutoConfiguration.class }) @TestPropertySource(locations = "classpath:/jpa-test.properties") public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest { diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java index d14985f272..a1fa814e3c 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java @@ -54,7 +54,8 @@ import com.fasterxml.jackson.dataformat.cbor.CBORParser; @ContextConfiguration(classes = { DdiApiConfiguration.class, RestConfiguration.class, - RepositoryApplicationConfiguration.class, TestConfiguration.class, TestSupportBinderAutoConfiguration.class }) + RepositoryApplicationConfiguration.class, TestConfiguration.class, + TestSupportBinderAutoConfiguration.class }) @TestPropertySource(locations = "classpath:/ddi-test.properties") public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrationTest { diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/AbstractManagementApiIntegrationTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/AbstractManagementApiIntegrationTest.java index 309315707c..ce2ee12ef4 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/AbstractManagementApiIntegrationTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/AbstractManagementApiIntegrationTest.java @@ -30,7 +30,8 @@ import org.springframework.test.web.servlet.ResultMatcher; @ContextConfiguration(classes = { MgmtApiConfiguration.class, RestConfiguration.class, - RepositoryApplicationConfiguration.class, TestConfiguration.class, TestSupportBinderAutoConfiguration.class }) + RepositoryApplicationConfiguration.class, TestConfiguration.class, + TestSupportBinderAutoConfiguration.class }) @TestPropertySource(locations = "classpath:/mgmt-test.properties") public abstract class AbstractManagementApiIntegrationTest extends AbstractRestIntegrationTest { diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java index ce20c076e1..21aa96445a 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java @@ -71,7 +71,8 @@ @Feature("Documentation Verification - API") @ExtendWith(RestDocumentationExtension.class) @ContextConfiguration(classes = { DdiApiConfiguration.class, MgmtApiConfiguration.class, RestConfiguration.class, - RepositoryApplicationConfiguration.class, TestConfiguration.class, TestSupportBinderAutoConfiguration.class }) + RepositoryApplicationConfiguration.class, TestConfiguration.class, + TestSupportBinderAutoConfiguration.class }) @TestPropertySource(locations = { "classpath:/updateserver-restdocumentation-test.properties" }) public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrationTest { diff --git a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java index 76d9f9d843..7217b489ce 100644 --- a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java +++ b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.PropertySource; import org.springframework.http.HttpHeaders; import org.springframework.security.test.context.support.WithUserDetails; import org.springframework.test.web.servlet.ResultActions; @@ -25,10 +26,15 @@ import io.qameta.allure.Feature; import io.qameta.allure.Story; -@SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.server.security.cors.enabled=true", - "hawkbit.server.security.cors.allowedOrigins=" + CorsTest.ALLOWED_ORIGIN_FIRST + "," +@SpringBootTest(properties = { + "hawkbit.dmf.rabbitmq.enabled=false", + "hawkbit.server.security.cors.enabled=true", + "hawkbit.server.security.cors.allowedOrigins=" + + CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_SECOND, - "hawkbit.server.security.cors.exposedHeaders=Access-Control-Allow-Origin" })@Feature("Integration Test - Security") + "hawkbit.server.security.cors.exposedHeaders=" + + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN}) +@Feature("Integration Test - Security") @Story("CORS") public class CorsTest extends AbstractSecurityTest { diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index 41592dd8d7..29b7b83c78 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -149,6 +149,16 @@ hawkbit-repository-api ${project.version} + + org.eclipse.hawkbit + hawkbit-http-security + ${project.version} + + + org.eclipse.hawkbit + hawkbit-autoconfigure + ${project.version} + commons-io commons-io diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/MgmtUiAutoConfiguration.java similarity index 98% rename from hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java rename to hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/MgmtUiAutoConfiguration.java index 713328029f..417c88d7e2 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/MgmtUiAutoConfiguration.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.autoconfigure.mgmt.ui; +package org.eclipse.hawkbit.ui.autoconfigure; import java.util.concurrent.ScheduledExecutorService; diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/RedirectController.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/RedirectController.java similarity index 94% rename from hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/RedirectController.java rename to hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/RedirectController.java index 9fd9049e33..ef902cd347 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/RedirectController.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/RedirectController.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.autoconfigure.mgmt.ui; +package org.eclipse.hawkbit.ui.autoconfigure; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/UISecurityConfigurationAdapter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/UISecurityConfigurationAdapter.java new file mode 100644 index 0000000000..01e50b365d --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/autoconfigure/UISecurityConfigurationAdapter.java @@ -0,0 +1,306 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.autoconfigure; + +import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; +import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.security.DosFilter; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.ui.MgmtUiConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.annotation.Order; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; +import org.springframework.security.config.annotation.web.WebSecurityConfigurer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.firewall.FirewalledRequest; +import org.springframework.security.web.firewall.HttpFirewall; +import org.springframework.security.web.firewall.StrictHttpFirewall; +import org.springframework.security.web.session.HttpSessionEventPublisher; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.vaadin.spring.http.HttpService; +import org.vaadin.spring.security.annotation.EnableVaadinSharedSecurity; +import org.vaadin.spring.security.config.VaadinSharedSecurityConfiguration; +import org.vaadin.spring.security.shared.VaadinAuthenticationSuccessHandler; +import org.vaadin.spring.security.shared.VaadinUrlAuthenticationSuccessHandler; +import org.vaadin.spring.security.web.VaadinRedirectStrategy; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * {@link WebSecurityConfigurer} for external (management) access. + */ +@Configuration +@EnableWebSecurity +@EnableVaadinSharedSecurity +@ConditionalOnClass(MgmtUiConfiguration.class) +public class UISecurityConfigurationAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(UISecurityConfigurationAdapter.class); + + private static final int DOS_FILTER_ORDER = -200; + + @Autowired + private HawkbitSecurityProperties hawkbitSecurityProperties; + + /** + * Filter to protect the hawkBit management UI against to many requests. + * + * @param securityProperties for filter configuration + * @return the spring filter registration bean for registering a denial + * of service protection filter in the filter chain + */ + @Bean + @ConditionalOnProperty(prefix = "hawkbit.server.security.dos.ui-filter", name = "enabled", matchIfMissing = true) + public FilterRegistrationBean dosMgmtUiFilter(final HawkbitSecurityProperties securityProperties) { + final HawkbitSecurityProperties.Dos.Filter filterProperties = securityProperties.getDos().getUiFilter(); + final HawkbitSecurityProperties.Clients clientProperties = securityProperties.getClients(); + + final FilterRegistrationBean filterRegBean = new FilterRegistrationBean<>(); + + filterRegBean.setFilter(new DosFilter(null, filterProperties.getMaxRead(), + filterProperties.getMaxWrite(), filterProperties.getWhitelist(), clientProperties.getBlacklist(), + clientProperties.getRemoteIpHeader())); + + // All URLs that can be called anonymous + filterRegBean.setUrlPatterns(Arrays.asList("/UI/login", "/UI/login/*", "/UI/logout", "/UI/logout/*")); + filterRegBean.setOrder(DOS_FILTER_ORDER); + filterRegBean.setName("dosMgmtUiFilter"); + + return filterRegBean; + } + + @Bean + AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + /** + * Overwriting VaadinAuthenticationSuccessHandler of default + * VaadinSharedSecurityConfiguration + * + * @return the vaadin success authentication handler + */ + @Primary + @Bean(name = VaadinSharedSecurityConfiguration.VAADIN_AUTHENTICATION_SUCCESS_HANDLER_BEAN) + public VaadinAuthenticationSuccessHandler redirectSaveHandler(final HttpService httpService, + final VaadinRedirectStrategy redirectStrategy) { + final VaadinUrlAuthenticationSuccessHandler handler = new TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler( + httpService, redirectStrategy, "/UI/"); + handler.setTargetUrlParameter("r"); + + return handler; + } + + /** + * Listener to redirect to login page after session timeout. Close the + * vaadin session, because it's is not possible to redirect in + * atmosphere. + * + * @return the servlet listener. + */ + @Bean + public ServletListenerRegistrationBean httpSessionEventPublisher() { + return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher()); + } + + @Bean + @Order(400) + protected SecurityFilterChain filterChainUI( + final HttpSecurity http, + @Autowired(required = false) + final OAuth2UserService oidcUserService, + @Autowired(required = false) + final AuthenticationSuccessHandler authenticationSuccessHandler, + final LogoutHandler logoutHandler, + final LogoutSuccessHandler logoutSuccessHandler) + throws Exception { + final boolean enableOidc = oidcUserService != null && authenticationSuccessHandler != null; + + // workaround regex: we need to exclude the URL /UI/HEARTBEAT here + // because we bound the vaadin application to /UI and not to root, + // described in vaadin-forum: + // https://vaadin.com/forum#!/thread/3200565. + HttpSecurity httpSec; + if (enableOidc) { + httpSec = http.requestMatchers().antMatchers("/**/UI/**", "/**/oauth2/**").and(); + } else { + httpSec = http.antMatcher("/**/UI/**"); + } + // disable as CSRF is handled by Vaadin + httpSec.csrf(AbstractHttpConfigurer::disable); + // allow same origin X-Frame-Options for correct file download under + // Safari + httpSec.headers().frameOptions().sameOrigin(); + + if (hawkbitSecurityProperties.isRequireSsl()) { + httpSec = httpSec.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); + } else { + LOG.info( + """ + ****************** + ** Requires HTTPS Security has been disabled for UI, should only be used for developing purposes ** + ******************"""); + } + + if (!ObjectUtils.isEmpty(hawkbitSecurityProperties.getContentSecurityPolicy())) { + httpSec.headers().contentSecurityPolicy(hawkbitSecurityProperties.getContentSecurityPolicy()); + } + + // UI + httpSec.authorizeRequests().antMatchers("/UI/login/**", "/UI/UIDL/**").permitAll().anyRequest() + .authenticated(); + + if (enableOidc) { + // OIDC + httpSec.oauth2Login().userInfoEndpoint().oidcUserService(oidcUserService).and() + .successHandler(authenticationSuccessHandler).and().oauth2Client(); + } else { + // UI login / Basic auth + httpSec.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/UI/login")); + } + + // UI logout + httpSec.logout().logoutUrl("/UI/logout*").addLogoutHandler(logoutHandler) + .logoutSuccessHandler(logoutSuccessHandler); + + return httpSec.build(); + } + + /** + * HttpFirewall which enables to define a list of allowed host names. + * + * @return the http firewall. + */ + @Bean + public HttpFirewall httpFirewall() { + final List allowedHostNames = hawkbitSecurityProperties.getAllowedHostNames(); + final IgnorePathsStrictHttpFirewall firewall = new IgnorePathsStrictHttpFirewall( + hawkbitSecurityProperties.getHttpFirewallIgnoredPaths()); + + if (!CollectionUtils.isEmpty(allowedHostNames)) { + firewall.setAllowedHostnames(hostName -> { + LOG.debug("Firewall check host: {}, allowed: {}", hostName, allowedHostNames.contains(hostName)); + return allowedHostNames.contains(hostName); + }); + } + return firewall; + } + + private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall { + + private final Collection pathsToIgnore; + + public IgnorePathsStrictHttpFirewall(final Collection pathsToIgnore) { + super(); + this.pathsToIgnore = pathsToIgnore; + } + + @Override + public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) { + if (pathsToIgnore != null && pathsToIgnore.contains(request.getRequestURI())) { + return new FirewalledRequest(request) { + @Override + public void reset() { + // nothing to do + } + }; + } + return super.getFirewalledRequest(request); + } + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + // No security for static content + return (web) -> web.ignoring().antMatchers("/documentation/**", "/VAADIN/**", "/*.*", "/docs/**"); + } + + /** + * Configuration that defines the {@link AccessDecisionManager} bean for + * UI method security used by the Vaadin Servlet. Notice: we can not use + * the top-level method security configuration because + * AdviceMode.ASPECTJ is not supported. + */ + @Configuration + @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true) + @ConditionalOnClass(MgmtUiConfiguration.class) + static class UIMethodSecurity extends GlobalMethodSecurityConfiguration { + + @Bean(name = VaadinSharedSecurityConfiguration.ACCESS_DECISION_MANAGER_BEAN) + @Override + protected AccessDecisionManager accessDecisionManager() { + return super.accessDecisionManager(); + } + } + + /** + * After a successful login on the UI we need to ensure to create the tenant + * meta data within SP. + */ + class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends VaadinUrlAuthenticationSuccessHandler { + + @Autowired + private SystemManagement systemManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + public TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler(final HttpService http, + final VaadinRedirectStrategy redirectStrategy, final String defaultTargetUrl) { + super(http, redirectStrategy, defaultTargetUrl); + } + + @Override + public void onAuthenticationSuccess(final Authentication authentication) throws Exception { + systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, getTenantFrom(authentication)); + + super.onAuthenticationSuccess(authentication); + } + + private static String getTenantFrom(final Authentication authentication) { + final Object details = authentication.getDetails(); + if (details instanceof TenantAwareAuthenticationDetails) { + return ((TenantAwareAuthenticationDetails) details).getTenant(); + } + + throw new InsufficientAuthenticationException("Authentication details/tenant info are not specified!"); + } + } +} diff --git a/hawkbit-ui/src/main/resources/META-INF/spring.factories b/hawkbit-ui/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..877556b917 --- /dev/null +++ b/hawkbit-ui/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.eclipse.hawkbit.ui.autoconfigure.MgmtUiAutoConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index 801e798ad8..e805283bda 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.12 + 2.7.13 org.eclipse.hawkbit @@ -127,16 +127,14 @@ + true 17 - 2.7.12 - 5.3.26 + 2.7.13 2021.0.5 2.0.0.RELEASE - true - @@ -148,11 +146,6 @@ 2.14.2 - - 1.2.9 - - - 5.7.7 3.12.1 @@ -853,11 +846,6 @@ spring-plugin-core ${spring.plugin.core.version} - - org.springframework.security - spring-security-oauth2-client - ${spring-security-oauth2-client.version} -