diff --git a/smp-parent-pom/pom.xml b/smp-parent-pom/pom.xml index 84dbbc31682f27c4af7581a8a94b4ff3643589b8..0124d36eacc751718f5b00540cf3ca18577b3416 100644 --- a/smp-parent-pom/pom.xml +++ b/smp-parent-pom/pom.xml @@ -50,8 +50,8 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <slf4j.version>1.7.26</slf4j.version> - <spring.version>5.1.9.RELEASE</spring.version> - <spring.security.version>5.1.6.RELEASE</spring.security.version> + <spring.version>5.3.9</spring.version> + <spring.security.version>5.5.2</spring.security.version> <spring.boot.version>2.1.8.RELEASE</spring.boot.version> <!-- bdmsl.client.version>3.0.0</bdmsl.client.version --> <bdmsl.api.version>4.0.0</bdmsl.api.version> diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java index e827e0dd8d5283901dc24ce7d0d415cfa9b828a3..a99e7e32fe5814729ba635946852de083809fb11 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java @@ -56,7 +56,13 @@ public enum SMPPropertyEnum { SML_PROXY_PORT("bdmsl.integration.proxy.port","","Deprecated", false, false , SMPPropertyTypeEnum.INTEGER), SML_PROXY_USER("bdmsl.integration.proxy.user","","Deprecated", false, false , SMPPropertyTypeEnum.STRING), SML_PROXY_PASSWORD("bdmsl.integration.proxy.password","","Deprecated", false, false , SMPPropertyTypeEnum.STRING), - SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression","0 48 */1 * * *","Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false , SMPPropertyTypeEnum.STRING); + SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression","0 48 */1 * * *","Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false , SMPPropertyTypeEnum.STRING), + + UI_COOKIE_SESSION_SECURE("smp.ui.session.secure","false","Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistent to man-in-the-middle attacks.", false, false , SMPPropertyTypeEnum.BOOLEAN), + UI_COOKIE_SESSION_MAX_AGE("smp.ui.session.max-age","","Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. Empty value will not set parameter", false, false , SMPPropertyTypeEnum.INTEGER), + UI_COOKIE_SESSION_SITE("smp.ui.session.strict","None","Controls whether a cookie is sent with cross-origin requests, providing some protection against cross-site request forgery attacks. Possible values are: Strict, None, Lax", false, false , SMPPropertyTypeEnum.STRING), + UI_COOKIE_SESSION_PATH("smp.ui.session.path","","A path that must exist in the requested URL, or the browser won't send the Cookie header. Null/Empty value sets the authentication requests context by default. The forward slash (/) character is interpreted as a directory separator, and subdirectories will be matched as well: for Path=/docs, /docs, /docs/Web/, and /docs/Web/HTTP will all match", false, false , SMPPropertyTypeEnum.STRING), + ; String property; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java index 970c4f31cb2e7436a76653eff4a727f5541c5f80..b526bb6744f0adc9beb27a17a2a409cdcb3f2272 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java @@ -170,4 +170,18 @@ public class ConfigurationService { } + public boolean getSessionCookieSecure() { + Boolean value = (Boolean) configurationDAO.getCachedPropertyValue(UI_COOKIE_SESSION_SECURE); + return value != null && value; + } + public Integer getSessionCookieMaxAge() { + return (Integer) configurationDAO.getCachedPropertyValue(UI_COOKIE_SESSION_MAX_AGE); + } + public String getSessionCookieSameSite() { + return (String) configurationDAO.getCachedPropertyValue(UI_COOKIE_SESSION_SITE); + } + public String getSessionCookiePath() { + return (String) configurationDAO.getCachedPropertyValue(UI_COOKIE_SESSION_PATH); + } + } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceMetadataIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceMetadataIntegrationTest.java index 576857da9c52dcc78a12b5e804fb4d20a5ffa742..ae2fffc2507b8b673b552ed681bd69be9e3a0eaf 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceMetadataIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceMetadataIntegrationTest.java @@ -50,8 +50,7 @@ import static eu.europa.ec.edelivery.smp.conversion.ServiceMetadataConverter.unm import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*; import static eu.europa.ec.edelivery.smp.testutil.XmlTestUtils.loadDocumentAsByteArray; import static eu.europa.ec.edelivery.smp.testutil.XmlTestUtils.marshallToByteArray; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Created by gutowpa on 15/11/2017. @@ -114,8 +113,6 @@ public class ServiceMetadataIntegrationTest extends AbstractServiceIntegrationTe @Test public void saveAndReadPositiveScenario() throws IOException, TransformerException, JAXBException { - - //given byte[] inServiceMetadataXml = loadDocumentAsByteArray(SERVICE_METADATA_XML_PATH); byte[] expectedSignedServiceMetadataXml = loadDocumentAsByteArray(SIGNED_SERVICE_METADATA_XML_PATH); @@ -131,7 +128,7 @@ public class ServiceMetadataIntegrationTest extends AbstractServiceIntegrationTe assertEquals(1, docIdsAfter.size()); assertEquals(DOC_ID.getValue().toLowerCase(), docIdsAfter.get(0).getValue()); // normalized assertEquals(DOC_ID.getScheme().toLowerCase(), docIdsAfter.get(0).getScheme()); // normalized - assertTrue(Arrays.equals(expectedSignedServiceMetadataXml, ServiceMetadataConverter.toByteArray(outServiceMetadataDoc) )); + assertArrayEquals(expectedSignedServiceMetadataXml, ServiceMetadataConverter.toByteArray(outServiceMetadataDoc)); } @Test diff --git a/smp-webapp/pom.xml b/smp-webapp/pom.xml index 377f0e545d80290cb83ec2c6b29f4c3fbb3facae..a2681a7d81a62f89c58375d81bbd3ce8f7ecad6b 100644 --- a/smp-webapp/pom.xml +++ b/smp-webapp/pom.xml @@ -15,7 +15,7 @@ <properties> <maven.deploy.skip>false</maven.deploy.skip> <buildtimestamp>${maven.build.timestamp}</buildtimestamp> - <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format> + <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss'Z'</maven.build.timestamp.format> <ftp.host>wltdgt02.cc.cec.eu.int</ftp.host> <ftp.port>2059</ftp.port> diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java index db303baa7c5659aa11c432882c5ab5456cb50d9b..6ee66b53a77dd6189d2fe18abff50065b247e2ae 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java @@ -20,6 +20,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.stereotype.Component; import java.security.cert.CertificateRevokedException; import java.text.DateFormat; @@ -33,6 +34,7 @@ import static java.util.Locale.US; @Import({SmpAppConfig.class}) +@Component public class SMPAuthenticationProvider implements AuthenticationProvider { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(AuthenticationProvider.class); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..27102a76c922e2233b8d1e18ac6df6bb88ecab37 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java @@ -0,0 +1,28 @@ +package eu.europa.ec.edelivery.smp.config; + + +import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +@Configuration +@EnableScheduling +@ComponentScan( + basePackages = "eu.europa.ec.edelivery.smp") +public class SMPTaskSchedulerConfig { + + ConfigurationDao configurationDao; + + @Autowired + public SMPTaskSchedulerConfig(ConfigurationDao configurationDao) { + this.configurationDao = configurationDao; + } + + @Scheduled(cron = "${smp.property.refresh.cronJobExpression:0 48 */1 * * *}") + public void refreshProperties() { + configurationDao.refreshProperties(); + } +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java index f9d35066613d5c183fb418a696d5ef432404f1e8..f6e6887ff469ac5e45f11ddbc716b1b12afd5c17 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java @@ -14,6 +14,8 @@ package eu.europa.ec.edelivery.smp.config; import eu.europa.ec.edelivery.smp.error.ErrorMappingControllerAdvice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -38,6 +40,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; "eu.europa.ec.edelivery.smp.ui"}) @Import({GlobalMethodSecurityConfig.class, ErrorMappingControllerAdvice.class}) public class SmpWebAppConfig implements WebMvcConfigurer { + private static final Logger LOG = LoggerFactory.getLogger(SmpWebAppConfig.class); @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java index 8a964e03cecb86fde3bb53e16d7521a82e9940fb..569bf7e2a1bd27a1372ebe93233cd441e0c89ddb 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java @@ -15,23 +15,142 @@ package eu.europa.ec.edelivery.smp.config; import eu.europa.ec.edelivery.security.BlueCoatAuthenticationFilter; import eu.europa.ec.edelivery.security.EDeliveryX509AuthenticationFilter; +import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationProvider; +import eu.europa.ec.edelivery.smp.auth.SMPAuthority; +import eu.europa.ec.edelivery.smp.error.SpringSecurityExceptionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ImportResource; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.firewall.DefaultHttpFirewall; +import org.springframework.security.web.firewall.HttpFirewall; /** * Created by gutowpa on 12/07/2017. */ @EnableWebSecurity -@ImportResource("classpath:spring-security.xml") +@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) +//@ImportResource("classpath:spring-security.xml") @ComponentScan("eu.europa.ec.edelivery.smp.auth") -public class SpringSecurityConfig { +public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { + private static final Logger LOG = LoggerFactory.getLogger(SpringSecurityConfig.class); + SMPAuthenticationProvider smpAuthenticationProvider; + BlueCoatAuthenticationFilter blueCoatAuthenticationFilter; + EDeliveryX509AuthenticationFilter x509AuthenticationFilter; + @Value("${authentication.blueCoat.enabled:false}") + boolean clientCertEnabled; + @Value("${encodedSlashesAllowedInUrl:true}") + boolean encodedSlashesAllowedInUrl; + + /** + * Initialize beans. Use lazy initialization for filter to avoid circular dependencies + * + * @param smpAuthenticationProvider + * @param blueCoatAuthenticationFilter + * @param x509AuthenticationFilter + */ + @Autowired + public SpringSecurityConfig(SMPAuthenticationProvider smpAuthenticationProvider, + @Lazy BlueCoatAuthenticationFilter blueCoatAuthenticationFilter, + @Lazy EDeliveryX509AuthenticationFilter x509AuthenticationFilter) { + super(false); + this.smpAuthenticationProvider = smpAuthenticationProvider; + this.blueCoatAuthenticationFilter = blueCoatAuthenticationFilter; + this.x509AuthenticationFilter = x509AuthenticationFilter; + } + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + + // prepare filters + blueCoatAuthenticationFilter.setBlueCoatEnabled(clientCertEnabled); + + httpSecurity.csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and() + .exceptionHandling().authenticationEntryPoint(new SpringSecurityExceptionHandler()).and() + .headers().frameOptions().deny().contentTypeOptions().and().xssProtection().xssProtectionEnabled(true).and().and() + + .addFilter(blueCoatAuthenticationFilter) + .addFilter(x509AuthenticationFilter) + .httpBasic() + .and() // username + .anonymous().authorities(SMPAuthority.S_AUTHORITY_ANONYMOUS.getAuthority()).and() + .authorizeRequests().antMatchers(HttpMethod.DELETE, "/ui/rest/security/authentication").permitAll() + .antMatchers(HttpMethod.POST, "/ui/rest/security/authentication").permitAll() + .and() + .authorizeRequests() + .antMatchers(HttpMethod.DELETE).hasAnyAuthority( + SMPAuthority.S_AUTHORITY_SMP_ADMIN.getAuthority(), + SMPAuthority.S_AUTHORITY_SERVICE_GROUP.getAuthority(), + SMPAuthority.S_AUTHORITY_SYSTEM_ADMIN.getAuthority()) + .antMatchers(HttpMethod.PUT).hasAnyAuthority( + SMPAuthority.S_AUTHORITY_SMP_ADMIN.getAuthority(), + SMPAuthority.S_AUTHORITY_SERVICE_GROUP.getAuthority(), + SMPAuthority.S_AUTHORITY_SYSTEM_ADMIN.getAuthority()) + .antMatchers(HttpMethod.GET).permitAll().and() + .authorizeRequests().antMatchers(HttpMethod.GET, "/ui/").hasAnyAuthority( + SMPAuthority.S_AUTHORITY_SMP_ADMIN.getAuthority(), + SMPAuthority.S_AUTHORITY_SERVICE_GROUP.getAuthority(), + SMPAuthority.S_AUTHORITY_SYSTEM_ADMIN.getAuthority()).and() + ; + + + } + + @Override + public void configure(WebSecurity web) throws Exception { + super.configure(web); + web.httpFirewall(smpHttpFirewall()); + } + + + @Bean + public HttpFirewall smpHttpFirewall() { + DefaultHttpFirewall firewall = new DefaultHttpFirewall(); + firewall.setAllowUrlEncodedSlash(encodedSlashesAllowedInUrl); + return firewall; + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) { + LOG.info("configureAuthenticationManagerBuilder, set SMP provider "); + auth.authenticationProvider(smpAuthenticationProvider); + } + + @Override + @Bean(name = {BeanIds.AUTHENTICATION_MANAGER, "smpAuthenticationManager"}) + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public BlueCoatAuthenticationFilter getClientCertAuthenticationFilter(@Qualifier("smpAuthenticationManager") AuthenticationManager authenticationManager) { + BlueCoatAuthenticationFilter blueCoatAuthenticationFilter = new BlueCoatAuthenticationFilter(); + blueCoatAuthenticationFilter.setAuthenticationManager(authenticationManager); + return blueCoatAuthenticationFilter; + } + + @Bean + public EDeliveryX509AuthenticationFilter getEDeliveryX509AuthenticationFilter(@Qualifier("smpAuthenticationManager") AuthenticationManager authenticationManager) { + EDeliveryX509AuthenticationFilter x509AuthenticationFilter = new EDeliveryX509AuthenticationFilter(); + x509AuthenticationFilter.setAuthenticationManager(authenticationManager); + return x509AuthenticationFilter; + } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java index 7d8418e8c0acfb5220e87b22028bb3e2cadd2fb3..fb3fb8c41500f44b62f671696c79f7f2bf524356 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java @@ -24,7 +24,6 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.bind.JAXBContext; @@ -51,20 +50,20 @@ public class SpringSecurityExceptionHandler extends BasicAuthenticationEntryPoin @Override public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { + AuthenticationException authException) throws IOException { String errorMsg = authException.getMessage(); - if(authException instanceof BadCredentialsException){ + if (authException instanceof BadCredentialsException) { errorMsg += " - Provided username/password or client certificate are invalid"; } handle(response, authException, errorMsg); } @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { handle(response, accessDeniedException, accessDeniedException.getMessage()); } - private void handle(HttpServletResponse response, RuntimeException exception, String errorMsg) throws IOException, ServletException { + private void handle(HttpServletResponse response, RuntimeException exception, String errorMsg) throws IOException { ResponseEntity respEntity = buildAndWarn(exception, errorMsg); String errorBody = marshall((ErrorResponse) respEntity.getBody()); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java index 2ae1ded9b8f3c5de61e71199635b8a0f58412433..aaf581ed253d61450e2cef1dbe98b7b7c7cd9012 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java @@ -10,6 +10,7 @@ import eu.europa.ec.edelivery.smp.data.ui.LoginRO; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -34,6 +35,9 @@ import javax.servlet.http.HttpServletResponse; @RequestMapping(value = "/ui/rest/security") public class AuthenticationResource { + public static final String CSRF_COOKIE_NAME = "XSRF-TOKEN"; + public static final String SESSION_COOKIE_NAME = "JSESSIONID"; + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(AuthenticationResource.class); @Autowired @@ -45,6 +49,12 @@ public class AuthenticationResource { @Autowired private ConversionService conversionService; + @Autowired + private ConfigurationService configurationService; + + SMPCookieWriter smpCookieWriter = new SMPCookieWriter(); + + @ResponseStatus(value = HttpStatus.FORBIDDEN) @ExceptionHandler({AuthenticationException.class}) public ErrorRO handleException(Exception ex) { @@ -54,8 +64,12 @@ public class AuthenticationResource { @RequestMapping(value = "authentication", method = RequestMethod.POST) @Transactional(noRollbackFor = BadCredentialsException.class) - public UserRO authenticate(@RequestBody LoginRO loginRO, HttpServletResponse response) { + public UserRO authenticate(@RequestBody LoginRO loginRO, HttpServletRequest request, HttpServletResponse response) { LOG.debug("Authenticating user [{}]", loginRO.getUsername()); + // reset session id with login + + recreatedSessionCookie(request, response); + SMPAuthenticationToken authentication = (SMPAuthenticationToken) authenticationService.authenticate(loginRO.getUsername(), loginRO.getPassword()); UserRO userRO = conversionService.convert(authentication.getUser(), UserRO.class); return authorizationService.sanitize(userRO); @@ -70,7 +84,7 @@ public class AuthenticationResource { } LOG.info("Logging out user [{}]", auth.getName()); - new CookieClearingLogoutHandler("JSESSIONID", "XSRF-TOKEN").logout(request, response, null); + new CookieClearingLogoutHandler(SESSION_COOKIE_NAME, CSRF_COOKIE_NAME).logout(request, response, null); LOG.info("Cleared cookies"); new SecurityContextLogoutHandler().logout(request, response, auth); LOG.info("Logged out"); @@ -88,4 +102,22 @@ public class AuthenticationResource { return user; } + /** + * set cookie parameters https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + * + * @param request + * @param response + */ + public void recreatedSessionCookie(HttpServletRequest request, HttpServletResponse response) { + String sessionId = request.changeSessionId(); + smpCookieWriter.writeCookieToResponse(SESSION_COOKIE_NAME, + sessionId, + configurationService.getSessionCookieSecure(), configurationService.getSessionCookieMaxAge(), + configurationService.getSessionCookiePath(), + configurationService.getSessionCookieSameSite(), + null, + request, response + ); + } + } \ No newline at end of file diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SMPCookieWriter.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SMPCookieWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..42eb976ef40b48683006d1b1ee3341e7ec9aea2f --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SMPCookieWriter.java @@ -0,0 +1,80 @@ +package eu.europa.ec.edelivery.smp.ui; + +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpHeaders; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +public class SMPCookieWriter { + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPCookieWriter.class); + private static final String COOKIE_PARAM_DELIMITER = "; "; + private static final String COOKIE_PARAM_SECURE = "secure"; + private static final String COOKIE_PARAM_MAX_AGE = "Max-Age"; + private static final String COOKIE_PARAM_EXPIRES = "Expires"; + + private static final String COOKIE_PARAM_PATH = "Path"; + private static final String COOKIE_PARAM_HTTP_ONLY = "HttpOnly"; + private static final String COOKIE_PARAM_SAME_SITE = "SameSite"; + + /** + * set cookie parameters https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + */ + public void writeCookieToResponse(String cookieName, String cookieValue, boolean isSecure, Integer maxAge, String path, String sameSite, String domain, HttpServletRequest request, HttpServletResponse response) { + + + StringBuilder sb = new StringBuilder(); + sb.append(cookieName) + .append('=') + .append(cookieValue); + // set secure\ + if (isSecure) { + sb.append(COOKIE_PARAM_DELIMITER) + .append(COOKIE_PARAM_SECURE); + } + + sb.append(COOKIE_PARAM_DELIMITER) + .append(COOKIE_PARAM_HTTP_ONLY); + + if (maxAge != null && maxAge > -1) { + sb.append(COOKIE_PARAM_DELIMITER) + .append(COOKIE_PARAM_MAX_AGE) + .append('=') + .append(maxAge.intValue()); + + ZonedDateTime expires = (maxAge != 0) ? ZonedDateTime.now().plusSeconds(maxAge) + : Instant.EPOCH.atZone(ZoneOffset.UTC); + sb.append(COOKIE_PARAM_DELIMITER).append(COOKIE_PARAM_EXPIRES) + .append('=').append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME)); + } + + + if (StringUtils.isBlank(path)) { + path = request.getContextPath(); + path = StringUtils.isNotBlank(path) + ? path : "/"; + } + if (StringUtils.isNotBlank(path)) { + sb.append(COOKIE_PARAM_DELIMITER) + .append(COOKIE_PARAM_PATH) + .append('=') + .append(path); + } + if (StringUtils.isNotBlank(sameSite)) { + sb.append(COOKIE_PARAM_DELIMITER) + .append(COOKIE_PARAM_SAME_SITE) + .append('=') + .append(sameSite); + } + + LOG.info("Set cookie [{}]",sb.toString()); + response.setHeader(HttpHeaders.SET_COOKIE, sb.toString()); + } +} diff --git a/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationClientCertTest.java b/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationClientCertTest.java index 9bc1d1ce5bdfdebf479ff333b4e6685c17938ee3..ce84508031a572bdf6f46ff2542f3360ba1bf462 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationClientCertTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationClientCertTest.java @@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. SmpWebAppConfig.class, DatabaseConfig.class, SpringSecurityConfig.class, - SpringSecurityTestConfig.class, + SpringSecurityTestConfig.class }) @WebAppConfiguration @Sql(scripts = {"classpath:/cleanup-database.sql", @@ -123,7 +123,6 @@ public class SecurityConfigurationClientCertTest { }, - { "Issue test one", "CN=ncp.fi.ehealth.testa.eu,O=Kansanelakelaitos,C=FI:f71ee8b11cb3b787", @@ -177,7 +176,7 @@ public class SecurityConfigurationClientCertTest { @Test public void validBlueCoatHeaderAuthorizedForPutTest() throws Exception { - System.out.println("Test: "+ testName); + System.out.println("Test: " + testName); String clientCert = buildClientCert(serialNumber, certificateDn); System.out.println("Client-Cert: " + clientCert); diff --git a/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationTest.java b/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationTest.java index df9a1b9af2009eba88b426ea31d79063222cdfc4..27877ddfde1c070f53e9249a78aabd1016c1a8d6 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SecurityConfigurationTest.java @@ -63,14 +63,11 @@ public class SecurityConfigurationTest { public static final String BLUE_COAT_VALID_HEADER_DB_UPPER_SN = "sno=BB66&subject=CN=common name UPPER database SN,O=org,C=BE&validfrom=Dec 6 17:41:42 2016 GMT&validto=Jul 9 23:59:00 2050 GMT&issuer=C=x,O=y,CN=z"; public static final String TEST_USERNAME_BLUE_COAT__DB_UPPER_SN = "CN=common name UPPER database SN,O=org,C=BE:000000000000bb66"; + public static final String BLUE_COAT_NOT_AUTHORIZED_HEADER = "sno=bb61&subject=C=BE,O=org,CN=common name not exists&validfrom=Dec 6 17:41:42 2016 GMT&validto=Jul 9 23:59:00 2050 GMT&issuer=C=x,O=y,CN=z"; + @Autowired private WebApplicationContext context; - /* - @PersistenceContext - private EntityManager em; - */ - MockMvc mvc; @Before @@ -151,6 +148,15 @@ public class SecurityConfigurationTest { .andExpect(content().string(TEST_USERNAME_BLUE_COAT)) .andReturn().getResponse().getContentAsString(); } + @Test + public void blueCoatHeaderNotAuthorizedForPutTest() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("Client-Cert", BLUE_COAT_NOT_AUTHORIZED_HEADER); + + mvc.perform(MockMvcRequestBuilders.put(RETURN_LOGGED_USER_PATH) + .headers(headers)) + .andExpect(status().isUnauthorized()); + } @Test public void validBlueCoatHeaderAuthorizedBeforeValidBasicAuthTest() throws Exception { diff --git a/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SignatureValidatorTest.java b/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SignatureValidatorTest.java index 7523b93aaa601b6f83131e513d202f9615cbf8ef..5400c747fabc914d4f975c61449ed2121988a710 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SignatureValidatorTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/cipa/smp/server/security/SignatureValidatorTest.java @@ -125,7 +125,7 @@ public class SignatureValidatorTest { } @Test - public void validateLinarizedSignature() throws Throwable { + public void validateLinearizedSignature() throws Throwable { String serviceGroupId = "ehealth-actorid-qns::urn:brazil:ncpb"; Principal principal = new PreAuthenticatedCertificatePrincipal("C=BE, O=European Commission,OU=CEF_eDelivery.europa.eu,OU=eHealth,OU=SMP_TEST,CN=EHEALTH_SMP_EC", "C=DE, O=T-Systems International GmbH, OU=T-Systems Trust Center, ST=Nordrhein Westfalen/postalCode=57250, L=Netphen/street=Untere Industriestr. 20, CN=Shared Business CA 4", "f7:1e:e8:b1:1c:b3:b7:87"); String filePathToLoad = "/input/ServiceMetadata_linarized.xml"; @@ -181,8 +181,8 @@ public class SignatureValidatorTest { //Default signature validation Element smpSigPointer = SignatureUtil.findSignatureByParentNode(response.getDocumentElement()); SignatureUtil.validateSignature(smpSigPointer); - Assert.assertEquals(signedByCustomizedSignature, SignatureUtil.loadDocumentAsString(signedByCustomizedSignatureFilePath)); - Assert.assertEquals(SignatureUtil.marshall(response), SignatureUtil.loadDocumentAsString(defaultSignatureFilePath)); + Assert.assertEquals(SignatureUtil.loadDocumentAsString(signedByCustomizedSignatureFilePath), signedByCustomizedSignature); + Assert.assertEquals(SignatureUtil.loadDocumentAsString(defaultSignatureFilePath), SignatureUtil.marshall(response) ); } public static Document parse(String serviceMetadataXml) throws SAXException, IOException, ParserConfigurationException { diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupControllerTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupControllerTest.java index eabf3ba081ec2e5f06ca08cdd7967663278984d3..3585e368a18de25458e6e87748813c273c9093d6 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupControllerTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupControllerTest.java @@ -203,7 +203,7 @@ public class ServiceGroupControllerTest { } @Test - public void getExistingServiceMetadatWithReverseNoProxyHost() throws Exception { + public void getExistingServiceMetadataWithReverseNoProxyHost() throws Exception { //given prepareForGet();