diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SMPCookieWriter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriter.java similarity index 72% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SMPCookieWriter.java rename to smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriter.java index 42eb976ef40b48683006d1b1ee3341e7ec9aea2f..cfc4b072822aaaff27c30b632dfeb08f5c7063c3 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SMPCookieWriter.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriter.java @@ -1,8 +1,7 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.utils; 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; @@ -15,6 +14,10 @@ import java.time.format.DateTimeFormatter; public class SMPCookieWriter { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPCookieWriter.class); + + public static final String CSRF_COOKIE_NAME = "XSRF-TOKEN"; + public static final String SESSION_COOKIE_NAME = "JSESSIONID"; + 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"; @@ -27,7 +30,25 @@ public class SMPCookieWriter { /** * 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) { + public void writeCookieToResponse(String cookieName, String cookieValue, boolean isSecure, Integer maxAge, String path, String sameSite, HttpServletRequest request, HttpServletResponse response) { + String cookieHeader = generateSetCookieHeader(cookieName, cookieValue, isSecure, maxAge, path, sameSite, request); + LOG.info("Set cookie [{}]",cookieHeader); + response.setHeader(HttpHeaders.SET_COOKIE, cookieHeader); + } + + /** + * Method generates set cookie header + * + * @param cookieName + * @param cookieValue + * @param isSecure + * @param maxAge + * @param path + * @param sameSite + * @param request + * @return + */ + public String generateSetCookieHeader(String cookieName, String cookieValue, boolean isSecure, Integer maxAge, String path, String sameSite, HttpServletRequest request) { StringBuilder sb = new StringBuilder(); @@ -59,7 +80,7 @@ public class SMPCookieWriter { if (StringUtils.isBlank(path)) { path = request.getContextPath(); path = StringUtils.isNotBlank(path) - ? path : "/"; + ? path : "/"; } if (StringUtils.isNotBlank(path)) { sb.append(COOKIE_PARAM_DELIMITER) @@ -74,7 +95,7 @@ public class SMPCookieWriter { .append(sameSite); } - LOG.info("Set cookie [{}]",sb.toString()); - response.setHeader(HttpHeaders.SET_COOKIE, sb.toString()); + LOG.debug("generated set cookie header [{}]", sb.toString()); + return sb.toString(); } } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriterGenerateHeaderTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriterGenerateHeaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eff4b09eb6c4809c42edc236685fb7d34115351c --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriterGenerateHeaderTest.java @@ -0,0 +1,83 @@ +package eu.europa.ec.edelivery.smp.utils; + +import org.apache.commons.lang3.StringUtils; +import org.hamcrest.Matchers; +import org.hamcrest.core.IsNot; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpSession; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.Collection; +import java.util.UUID; + +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.Mockito.doReturn; + +@RunWith(Parameterized.class) +public class SMPCookieWriterGenerateHeaderTest { + + // parameters + String description; + boolean isSecure; + Integer maxAge; + String path; + String sameSite; + String expectedResultContains; + String expectedResultNotContains; + + @Parameterized.Parameters(name = "{index}: {0}") + public static Collection cookieWriterTestParameters() { + return Arrays.asList(new Object[][]{ + {"Contains HttpOnly", false, 36000, "/path", "Strict", "; HttpOnly", null}, + {"Test with secure off", false, 36000, "/path", "Strict", null, "; secure"}, + {"Test with secure on", true, 36000, "/path", "Strict", "; secure", null}, + {"MaxAge given", true, 123456, "/path", "Strict", "; Max-Age=123456; Expires=", null}, + {"MaxAge not given", true, null, "/path", "Strict", null, "; Max-Age="}, + {"SameSite: off", false, 36000, "/path", null, null, "; SameSite="}, + {"SameSite: Strict", true, 36000, "/path", "Strict", "; SameSite=Strict", null}, + {"SameSite: Lax", true, 36000, "/path", "Lax", "; SameSite=Lax", null}, + {"SameSite: None", true, 36000, "/path", "None", "; SameSite=None", null}, + {"Path: Null - set request context by default", true, 36000, null, "None", "; Path=/request-context;", null}, + {"Path: user-defined-path", true, 36000, "/user-defined-path", "None", "; Path=/user-defined-path", null}, + }); + } + + public SMPCookieWriterGenerateHeaderTest(String description, boolean isSecure, Integer maxAge, String path, String sameSite, String domain, String expectedResultContains, String expectedResultNotContains) { + this.description = description; + this.isSecure = isSecure; + this.maxAge = maxAge; + this.path = path; + this.sameSite = sameSite; + this.expectedResultContains = expectedResultContains; + this.expectedResultNotContains = expectedResultNotContains; + } + + // test instance + SMPCookieWriter testInstance = new SMPCookieWriter(); + + @Test + public void generateSetCookieHeader() { + // given + String sessionID = UUID.randomUUID().toString(); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + doReturn("/request-context").when(request).getContextPath(); + + // when + String result = testInstance.generateSetCookieHeader(MockHttpSession.SESSION_COOKIE_NAME, sessionID, isSecure, maxAge, path, sameSite, request); + + // then + assertThat(result, startsWith(MockHttpSession.SESSION_COOKIE_NAME+"="+sessionID)); + if (StringUtils.isNotEmpty(expectedResultContains)) { + assertThat(result, containsString(expectedResultContains)); + } + if (StringUtils.isNotEmpty(expectedResultNotContains)) { + assertThat(result, IsNot.not(containsString(expectedResultNotContains))); + } + } +} \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriterTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e8806ea30a0536331b35212710ca103e15ccc636 --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/SMPCookieWriterTest.java @@ -0,0 +1,45 @@ +package eu.europa.ec.edelivery.smp.utils; + +import org.apache.commons.lang3.StringUtils; +import org.hamcrest.core.IsNot; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mockito; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.MockHttpSession; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + + +public class SMPCookieWriterTest { + SMPCookieWriter testInstance = spy(new SMPCookieWriter()); + + @Test + public void generateSetCookieHeaderForName() { + // given + String generatedHeader = "JSESSION=this-is-test-example; HttpOnly; Max-Age=36000; Expires=Thu, 16 Sep 2021 19:41:30 +0200; Path=/path; SameSite=Strict"; + String sessionValue="SessionValue"; + boolean isSecure=true; + Integer maxAge =null; + String path =null; + String sameSite="Lax"; + HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + doReturn("/request-context").when(request).getContextPath(); + doReturn(generatedHeader).when(testInstance).generateSetCookieHeader(SMPCookieWriter.SESSION_COOKIE_NAME,sessionValue, isSecure, maxAge, path, sameSite, request); + + // when + testInstance.writeCookieToResponse(SMPCookieWriter.SESSION_COOKIE_NAME, sessionValue, isSecure, maxAge, path, sameSite, request, response); + // then + verify(response).setHeader( eq(HttpHeaders.SET_COOKIE), eq(generatedHeader)); + } +} \ No newline at end of file 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 aaf581ed253d61450e2cef1dbe98b7b7c7cd9012..680ebce5ada3cff6c8db379e4a969e55abf6b980 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 @@ -11,6 +11,7 @@ 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 eu.europa.ec.edelivery.smp.utils.SMPCookieWriter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -27,6 +28,10 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import static eu.europa.ec.edelivery.smp.auth.SMPAuthority.*; +import static eu.europa.ec.edelivery.smp.utils.SMPCookieWriter.CSRF_COOKIE_NAME; +import static eu.europa.ec.edelivery.smp.utils.SMPCookieWriter.SESSION_COOKIE_NAME; + /** * @author Sebastian-Ion TINCU * @since 4.0 @@ -35,9 +40,6 @@ 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 @@ -91,7 +93,7 @@ public class AuthenticationResource { } @RequestMapping(value = "user", method = RequestMethod.GET) - @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) + @Secured({S_AUTHORITY_TOKEN_SYSTEM_ADMIN, S_AUTHORITY_TOKEN_SMP_ADMIN, S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) public UserRO getUser() { UserRO user = new UserRO(); @@ -115,7 +117,6 @@ public class AuthenticationResource { configurationService.getSessionCookieSecure(), configurationService.getSessionCookieMaxAge(), configurationService.getSessionCookiePath(), configurationService.getSessionCookieSameSite(), - null, request, response ); }