Feature enable cors (#854)
* enabled cors in security configuration, added corresponding properties * added test for cors validation * formatting * added mariadb test dependency and refactored test * added database listeners to the test * Remove dependency duplicate Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com> Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
committed by
Stefan Behl
parent
4640b8ad5e
commit
379726a697
@@ -84,7 +84,11 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn
|
|||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
import org.vaadin.spring.security.VaadinSecurityContext;
|
import org.vaadin.spring.security.VaadinSecurityContext;
|
||||||
import org.vaadin.spring.security.annotation.EnableVaadinSecurity;
|
import org.vaadin.spring.security.annotation.EnableVaadinSecurity;
|
||||||
import org.vaadin.spring.security.web.VaadinRedirectStrategy;
|
import org.vaadin.spring.security.web.VaadinRedirectStrategy;
|
||||||
@@ -450,6 +454,7 @@ public class SecurityManagedConfiguration {
|
|||||||
* Security configuration for the REST management API.
|
* Security configuration for the REST management API.
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
@Order(350)
|
@Order(350)
|
||||||
@ConditionalOnClass(MgmtApiConfiguration.class)
|
@ConditionalOnClass(MgmtApiConfiguration.class)
|
||||||
public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
||||||
@@ -496,6 +501,11 @@ public class SecurityManagedConfiguration {
|
|||||||
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
||||||
|
|
||||||
HttpSecurity httpSec = http.regexMatcher("\\/rest.*|\\/system/admin.*").csrf().disable();
|
HttpSecurity httpSec = http.regexMatcher("\\/rest.*|\\/system/admin.*").csrf().disable();
|
||||||
|
|
||||||
|
if (securityProperties.getCors().isEnabled()) {
|
||||||
|
httpSec = httpSec.cors().and();
|
||||||
|
}
|
||||||
|
|
||||||
if (securityProperties.isRequireSsl()) {
|
if (securityProperties.isRequireSsl()) {
|
||||||
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
||||||
}
|
}
|
||||||
@@ -527,6 +537,22 @@ public class SecurityManagedConfiguration {
|
|||||||
httpSec.anonymous().disable();
|
httpSec.anonymous().disable();
|
||||||
httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.cors", name = "enabled", matchIfMissing = false)
|
||||||
|
CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
final CorsConfiguration restCorsConfiguration = new CorsConfiguration();
|
||||||
|
|
||||||
|
restCorsConfiguration.setAllowedOrigins(securityProperties.getCors().getAllowedOrigins());
|
||||||
|
restCorsConfiguration.setAllowCredentials(true);
|
||||||
|
restCorsConfiguration.setAllowedHeaders(securityProperties.getCors().getAllowedHeaders());
|
||||||
|
restCorsConfiguration.setAllowedMethods(securityProperties.getCors().getAllowedMethods());
|
||||||
|
|
||||||
|
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/rest/**", restCorsConfiguration);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -59,6 +59,34 @@
|
|||||||
<groupId>com.microsoft.sqlserver</groupId>
|
<groupId>com.microsoft.sqlserver</groupId>
|
||||||
<artifactId>mssql-jdbc</artifactId>
|
<artifactId>mssql-jdbc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-junit4</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mariadb.jdbc</groupId>
|
||||||
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
|
<artifactId>hawkbit-repository-test</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -33,3 +33,7 @@ spring.rabbitmq.port=5672
|
|||||||
#hawkbit.server.im.users[0].firstname=Eclipse
|
#hawkbit.server.im.users[0].firstname=Eclipse
|
||||||
#hawkbit.server.im.users[0].lastname=HawkBit
|
#hawkbit.server.im.users[0].lastname=HawkBit
|
||||||
#hawkbit.server.im.users[0].permissions=ALL
|
#hawkbit.server.im.users[0].permissions=ALL
|
||||||
|
|
||||||
|
# Enable CORS and specify the allowed origins:
|
||||||
|
#hawkbit.server.security.cors.enabled=true
|
||||||
|
#hawkbit.server.security.cors.allowedOrigins=http://localhost
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2019 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.app;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.MsSqlTestDatabase;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.MySqlTestDatabase;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.security.test.context.support.WithUserDetails;
|
||||||
|
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
|
||||||
|
import org.springframework.test.context.TestExecutionListeners;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.ResultActions;
|
||||||
|
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.test.context.TestExecutionListeners.MergeMode;
|
||||||
|
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@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})
|
||||||
|
@TestExecutionListeners(listeners = { MySqlTestDatabase.class, MsSqlTestDatabase.class },
|
||||||
|
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
|
||||||
|
@Feature("Integration Test - Security")
|
||||||
|
@Story("CORS")
|
||||||
|
public class CorsTest {
|
||||||
|
|
||||||
|
final static String ALLOWED_ORIGIN_FIRST = "http://test.first.origin";
|
||||||
|
final static String ALLOWED_ORIGIN_SECOND = "http://test.second.origin";
|
||||||
|
|
||||||
|
private final static String INVALID_ORIGIN = "http://test.invalid.origin";
|
||||||
|
private final static String INVALID_CORS_REQUEST = "Invalid CORS request";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebApplicationContext context;
|
||||||
|
|
||||||
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
final DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context)
|
||||||
|
.apply(SecurityMockMvcConfigurers.springSecurity()).dispatchOptions(true);
|
||||||
|
mvc = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithUserDetails("admin")
|
||||||
|
@Test
|
||||||
|
@Description("Ensures that Cors is working.")
|
||||||
|
public void validateCorsRequest() throws Exception {
|
||||||
|
performOptionsRequestToRestWithOrigin(ALLOWED_ORIGIN_FIRST).andExpect(status().isOk());
|
||||||
|
performOptionsRequestToRestWithOrigin(ALLOWED_ORIGIN_SECOND).andExpect(status().isOk());
|
||||||
|
|
||||||
|
final String invalidOriginResponseBody = performOptionsRequestToRestWithOrigin(INVALID_ORIGIN)
|
||||||
|
.andExpect(status().isForbidden()).andReturn().getResponse().getContentAsString();
|
||||||
|
assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST);
|
||||||
|
|
||||||
|
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST)
|
||||||
|
.andExpect(status().isForbidden()).andReturn().getResponse().getContentAsString();
|
||||||
|
assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResultActions performOptionsRequestToRestWithOrigin(final String origin) throws Exception {
|
||||||
|
return performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_V1_REQUEST_MAPPING, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResultActions performOptionsRequestToUrlWithOrigin(final String url, final String origin) throws Exception {
|
||||||
|
return mvc.perform(options(url).header("Access-Control-Request-Method", "GET").header("Origin", origin));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.security;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +23,7 @@ public class HawkbitSecurityProperties {
|
|||||||
|
|
||||||
private final Clients clients = new Clients();
|
private final Clients clients = new Clients();
|
||||||
private final Dos dos = new Dos();
|
private final Dos dos = new Dos();
|
||||||
|
private final Cors cors = new Cors();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content Security policy Header for Manager UI.
|
* Content Security policy Header for Manager UI.
|
||||||
@@ -68,6 +73,69 @@ public class HawkbitSecurityProperties {
|
|||||||
return clients;
|
return clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cors getCors() {
|
||||||
|
return cors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security configuration related to CORS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static class Cors {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to enable CORS.
|
||||||
|
*/
|
||||||
|
private boolean enabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed origins for CORS.
|
||||||
|
*/
|
||||||
|
private List<String> allowedOrigins = Collections.singletonList("http://localhost");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed headers for CORS.
|
||||||
|
*/
|
||||||
|
private List<String> allowedHeaders = Collections.singletonList("*");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed methods for CORS.
|
||||||
|
*/
|
||||||
|
private List<String> allowedMethods = Arrays.asList("DELETE", "GET", "POST", "PATCH", "PUT");
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(final boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllowedOrigins() {
|
||||||
|
return allowedOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedOrigins(final List<String> allowedOrigins) {
|
||||||
|
this.allowedOrigins = allowedOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllowedHeaders() {
|
||||||
|
return allowedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedHeaders(final List<String> allowedHeaders) {
|
||||||
|
this.allowedHeaders = allowedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllowedMethods() {
|
||||||
|
return allowedMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedMethods(final List<String> allowedMethods) {
|
||||||
|
this.allowedMethods = allowedMethods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security configuration related to clients.
|
* Security configuration related to clients.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user