[#1383] Spring Boot 3 Migration / Step 1 (#1384)

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 <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2023-07-17 10:36:26 +03:00
committed by GitHub
parent bcc2616e73
commit 56ea5b15c9
27 changed files with 582 additions and 583 deletions

View File

@@ -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(...)`

View File

@@ -31,12 +31,6 @@
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-ui</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-jpa</artifactId>

View File

@@ -53,10 +53,11 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken;
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<GrantedAuthority> 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());

View File

@@ -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.FilterConfig;
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.HttpDownloadAuthenticationFilter;
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.ConditionalOnClass;
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 @@ public class SecurityManagedConfiguration {
* {@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,9 +175,9 @@ public class SecurityManagedConfiguration {
*/
@Bean
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
public FilterRegistrationBean<DosFilter> dosDDiFilter(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Arrays.asList(DDI_ANT_MATCHERS),
public FilterRegistrationBean<DosFilter> dosFilterDDI(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> filterRegBean =
dosFilter(List.of(DDI_ANT_MATCHERS),
securityProperties.getDos().getFilter(), securityProperties.getClients());
filterRegBean.setOrder(DOS_FILTER_ORDER);
filterRegBean.setName("dosDDiFilter");
@@ -210,65 +185,69 @@ public class SecurityManagedConfiguration {
return filterRegBean;
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
@Bean
@Order(300)
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
http
.requestMatchers(requestMatchers -> requestMatchers.antMatchers(DDI_ANT_MATCHERS))
.csrf(AbstractHttpConfigurer::disable);
if (securityProperties.isRequireSsl()) {
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
}
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
LOG.info(
"""
******************
** Anonymous controller security enabled, should only be used for developing purposes **
******************""");
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
"controllerAnonymousFilter", "anonymous",
List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
http
.securityContext(AbstractHttpConfigurer::disable)
.anonymous(configurer -> configurer.authenticationFilter(anonymousFilter));
} else {
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
ddiSecurityConfiguration.getRp().getCnHeader(),
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
tenantAware, systemSecurityContext);
securityHeaderFilter.setAuthenticationManager(authenticationManager());
securityHeaderFilter.setAuthenticationManager(authenticationManager);
securityHeaderFilter.setCheckForPrincipalChanges(true);
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
securityTokenFilter.setAuthenticationManager(authenticationManager());
securityTokenFilter.setAuthenticationManager(authenticationManager);
securityTokenFilter.setCheckForPrincipalChanges(true);
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
tenantConfigurationManagement, tenantAware, systemSecurityContext);
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager());
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager);
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
HttpSecurity httpSec = http.csrf().disable();
if (securityProperties.isRequireSsl()) {
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
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));
}
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
LOG.info(
"******************\n** Anonymous controller security enabled, should only be used for developing purposes **\n******************");
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
"controllerAnonymousFilter", "anonymous",
Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
httpSec.requestMatchers().antMatchers(DDI_ANT_MATCHERS).and().securityContext().disable().anonymous()
.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);
}
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider(
ddiSecurityConfiguration.getRp().getTrustedIPs()));
return http.build();
}
}
@@ -277,9 +256,8 @@ public class SecurityManagedConfiguration {
* 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 @@ public class SecurityManagedConfiguration {
*/
@Bean
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
public FilterRegistrationBean<DosFilter> dosDDiDlFilter(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Arrays.asList(DDI_DL_ANT_MATCHER),
public FilterRegistrationBean<DosFilter> dosFilterDDIDL(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> 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 class SecurityManagedConfiguration {
return filterRegBean;
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
@Bean
@Order(301)
protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception {
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
http
.requestMatcher(new AntPathRequestMatcher(DDI_DL_ANT_MATCHER))
.csrf(AbstractHttpConfigurer::disable);
if (securityProperties.isRequireSsl()) {
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
}
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
LOG.info(
"""
******************
** Anonymous controller security enabled, should only be used for developing purposes **
******************""");
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
"controllerAnonymousFilter", "anonymous",
List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
http
.securityContext(AbstractHttpConfigurer::disable)
.anonymous(configurer -> configurer.authenticationFilter(anonymousFilter));
} else {
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
ddiSecurityConfiguration.getRp().getCnHeader(),
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
tenantAware, systemSecurityContext);
securityHeaderFilter.setAuthenticationManager(authenticationManager());
securityHeaderFilter.setAuthenticationManager(authenticationManager);
securityHeaderFilter.setCheckForPrincipalChanges(true);
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
securityTokenFilter.setAuthenticationManager(authenticationManager());
securityTokenFilter.setAuthenticationManager(authenticationManager);
securityTokenFilter.setCheckForPrincipalChanges(true);
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
tenantConfigurationManagement, tenantAware, systemSecurityContext);
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager());
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager);
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
final HttpControllerPreAuthenticateAnonymousDownloadFilter controllerAnonymousDownloadFilter = new HttpControllerPreAuthenticateAnonymousDownloadFilter(
tenantConfigurationManagement, tenantAware, systemSecurityContext);
controllerAnonymousDownloadFilter.setAuthenticationManager(authenticationManager());
controllerAnonymousDownloadFilter.setAuthenticationManager(authenticationManager);
controllerAnonymousDownloadFilter.setCheckForPrincipalChanges(true);
controllerAnonymousDownloadFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
HttpSecurity httpSec = http.csrf().disable();
if (securityProperties.isRequireSsl()) {
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
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));
}
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
LOG.info(
"******************\n** Anonymous controller security enabled, should only be used for developing purposes **\n******************");
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
"controllerAnonymousFilter", "anonymous",
Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
httpSec.requestMatchers().antMatchers(DDI_DL_ANT_MATCHER).and().securityContext().disable().anonymous()
.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);
}
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider(
ddiSecurityConfiguration.getRp().getTrustedIPs()));
return http.build();
}
}
@@ -407,10 +388,9 @@ public class SecurityManagedConfiguration {
@Bean
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
public FilterRegistrationBean<DosFilter> dosSystemFilter(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> 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 class SecurityManagedConfiguration {
private static FilterRegistrationBean<DosFilter> dosFilter(final Collection<String> includeAntPaths,
final HawkbitSecurityProperties.Dos.Filter filterProperties,
final HawkbitSecurityProperties.Clients clientProperties) {
final FilterRegistrationBean<DosFilter> filterRegBean = new FilterRegistrationBean<>();
filterRegBean.setFilter(new DosFilter(includeAntPaths, filterProperties.getMaxRead(),
@@ -435,36 +414,30 @@ public class SecurityManagedConfiguration {
*/
@Configuration
@EnableWebSecurity
@Order(320)
@ConditionalOnClass(MgmtApiConfiguration.class)
public static class IdRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
public static class IdRestSecurityConfigurationAdapter {
@Autowired
private DdiSecurityProperties ddiSecurityConfiguration;
@Autowired
private DownloadIdCache downloadIdCache;
@Override
protected void configure(final HttpSecurity http) throws Exception {
@Bean
@Order(320)
protected SecurityFilterChain filterChainDLID(
final HttpSecurity http,
final DdiSecurityProperties ddiSecurityConfiguration, final DownloadIdCache downloadIdCache)
throws Exception {
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
final HttpDownloadAuthenticationFilter downloadIdAuthenticationFilter = new HttpDownloadAuthenticationFilter(
downloadIdCache);
downloadIdAuthenticationFilter.setAuthenticationManager(authenticationManager());
downloadIdAuthenticationFilter.setAuthenticationManager(authenticationManager);
http.csrf().disable();
http.anonymous().disable();
http
.requestMatcher(new AntPathRequestMatcher("/**/downloadId/**"))
.authorizeHttpRequests(armrRepository -> 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 @@ public class SecurityManagedConfiguration {
* Security configuration for the REST management API.
*/
@Configuration
@Order(350)
@EnableWebSecurity
@ConditionalOnClass(MgmtApiConfiguration.class)
public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
public static class RestSecurityConfigurationAdapter {
@Autowired
@Lazy
private UserAuthenticationFilter userAuthenticationFilter;
private final HawkbitSecurityProperties securityProperties;
@Autowired(required = false)
private OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter;
@Autowired(required = false)
private InMemoryClientRegistrationRepository clientRegistrationRepository;
@Autowired
private SystemManagement systemManagement;
@Autowired
private 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<DosFilter> dosMgmtFilter(final HawkbitSecurityProperties securityProperties) {
public FilterRegistrationBean<DosFilter> dosFilterREST() {
final FilterRegistrationBean<DosFilter> 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 @@ public class SecurityManagedConfiguration {
: 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 class SecurityManagedConfiguration {
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 @@ public class SecurityManagedConfiguration {
}
}
/**
* {@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<OidcUserRequest, OidcUser> 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<DosFilter> dosMgmtUiFilter(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> 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> 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<String> 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<String> pathsToIgnore;
public IgnorePathsStrictHttpFirewall(final Collection<String> 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);
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 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);
}
@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
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -61,11 +61,6 @@
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>

View File

@@ -29,8 +29,6 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
import com.google.common.base.Throwables;
/**
*
*/
@@ -62,13 +60,14 @@ public class AmqpTestConfiguration {
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);
}
};
}

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.rabbitmq.test;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.UUID;
import javax.annotation.PreDestroy;
@@ -17,9 +18,8 @@ import javax.annotation.PreDestroy;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.junit.BrokerRunningSupport;
import org.springframework.util.StringUtils;
import org.springframework.util.ObjectUtils;
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 class 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 class RabbitMqSetupService {
@PreDestroy
public void deleteVirtualHost() {
if (StringUtils.isEmpty(virtualHost)) {
if (ObjectUtils.isEmpty(virtualHost)) {
return;
}
getRabbitmqHttpClient().deleteVhost(virtualHost);

View File

@@ -15,6 +15,7 @@ import java.util.Optional;
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 @@ import org.springframework.transaction.annotation.Transactional;
@NoRepositoryBean
@Transactional(readOnly = true)
public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity, I extends Serializable>
extends PagingAndSortingRepository<T, I>, NoCountSliceRepository<T> {
extends PagingAndSortingRepository<T, I>, CrudRepository<T, I>, NoCountSliceRepository<T> {
/**
* Retrieves an {@link BaseEntity} by its id.

View File

@@ -12,6 +12,7 @@ import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey;
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 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
public interface DistributionSetMetadataRepository
extends PagingAndSortingRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
CrudRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
JpaSpecificationExecutor<JpaDistributionSetMetadata> {
/**

View File

@@ -27,7 +27,8 @@ import org.springframework.data.repository.query.Param;
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)

View File

@@ -17,6 +17,7 @@ import org.springframework.data.domain.Page;
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 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
public interface SoftwareModuleMetadataRepository
extends PagingAndSortingRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
CrudRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/**

View File

@@ -12,6 +12,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
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 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
public interface TargetMetadataRepository
extends PagingAndSortingRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
CrudRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
JpaSpecificationExecutor<JpaTargetMetadata> {
/**

View File

@@ -30,7 +30,8 @@ import org.springframework.lang.NonNull;
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)

View File

@@ -14,6 +14,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
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 @@ import org.springframework.transaction.annotation.Transactional;
*
*/
@Transactional(readOnly = true)
public interface TenantMetaDataRepository extends PagingAndSortingRepository<JpaTenantMetaData, Long> {
public interface TenantMetaDataRepository
extends PagingAndSortingRepository<JpaTenantMetaData, Long>,
CrudRepository<JpaTenantMetaData, Long> {
/**
* Search {@link TenantMetaData} by tenant name.

View File

@@ -42,7 +42,8 @@ import org.springframework.transaction.annotation.Transactional;
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 {

View File

@@ -54,7 +54,8 @@ import com.fasterxml.jackson.dataformat.cbor.CBORGenerator;
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 {

View File

@@ -30,7 +30,8 @@ import org.springframework.test.context.TestPropertySource;
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 {

View File

@@ -71,7 +71,8 @@ import io.qameta.allure.Feature;
@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 {

View File

@@ -17,6 +17,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
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.Description;
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 {

View File

@@ -149,6 +149,16 @@
<artifactId>hawkbit-repository-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-http-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<DosFilter> dosMgmtUiFilter(final HawkbitSecurityProperties securityProperties) {
final HawkbitSecurityProperties.Dos.Filter filterProperties = securityProperties.getDos().getUiFilter();
final HawkbitSecurityProperties.Clients clientProperties = securityProperties.getClients();
final FilterRegistrationBean<DosFilter> 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> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
@Bean
@Order(400)
protected SecurityFilterChain filterChainUI(
final HttpSecurity http,
@Autowired(required = false)
final OAuth2UserService<OidcUserRequest, OidcUser> 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<String> 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<String> pathsToIgnore;
public IgnorePathsStrictHttpFirewall(final Collection<String> 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!");
}
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.eclipse.hawkbit.ui.autoconfigure.MgmtUiAutoConfiguration

18
pom.xml
View File

@@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<version>2.7.13</version>
</parent>
<groupId>org.eclipse.hawkbit</groupId>
@@ -127,16 +127,14 @@
</repositories>
<properties>
<snapshotDependencyAllowed>true</snapshotDependencyAllowed>
<java.version>17</java.version>
<spring.boot.version>2.7.12</spring.boot.version>
<spring-framework.version>5.3.26</spring-framework.version>
<spring.boot.version>2.7.13</spring.boot.version>
<spring.cloud.version>2021.0.5</spring.cloud.version>
<spring.plugin.core.version>2.0.0.RELEASE</spring.plugin.core.version>
<snapshotDependencyAllowed>true</snapshotDependencyAllowed>
<!-- Spring boot version overrides (should be reviewed with every boot
upgrade) - START -->
<!-- Newer versions needed than defined in Boot -->
@@ -148,11 +146,6 @@
<!-- CVE-2020-36518, CVE-2022-42003, CVE-2022-42004 -->
<jackson-bom.version>2.14.2</jackson-bom.version>
<!-- SONATYPE-2021-1175 -->
<logback.version>1.2.9</logback.version>
<!-- CVE-2022-31690 -->
<spring-security-oauth2-client.version>5.7.7</spring-security-oauth2-client.version>
<!-- Spring boot version overrides - END -->
<rabbitmq.http-client.version>3.12.1</rabbitmq.http-client.version>
@@ -853,11 +846,6 @@
<artifactId>spring-plugin-core</artifactId>
<version>${spring.plugin.core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>${spring-security-oauth2-client.version}</version>
</dependency>
<!-- Protostuff Io -->
<dependency>