summaryrefslogtreecommitdiff
path: root/eaaf_core
diff options
context:
space:
mode:
authorThomas <>2024-05-06 19:03:07 +0200
committerThomas <>2024-05-06 19:03:07 +0200
commit3454a41c5ecbff5e700efc16ee41cb11ec110e66 (patch)
treea2f7f067618b2b473c25812417a77b3a6d6df1a5 /eaaf_core
parent3654faef1801665ba74e43cdcf1fdd1ae359f52c (diff)
downloadEAAF-Components-3454a41c5ecbff5e700efc16ee41cb11ec110e66.tar.gz
EAAF-Components-3454a41c5ecbff5e700efc16ee41cb11ec110e66.tar.bz2
EAAF-Components-3454a41c5ecbff5e700efc16ee41cb11ec110e66.zip
feat(core): add optional extended HTTP request validator
Diffstat (limited to 'eaaf_core')
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java7
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java19
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java12
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java3
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/CookieBasedRequestValidator.java77
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/IHttpRequestValidator.java31
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/NothingHttpRequestValidatior.java28
-rw-r--r--eaaf_core/src/main/resources/messages/eaaf_core_messages.properties3
-rw-r--r--eaaf_core/src/test/java/at/gv/egiz/eaaf/core/test/impl/idp/validation/CookieBasedRequestValidatorTest.java115
9 files changed, 291 insertions, 4 deletions
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java
index 78653cf8..c93f872a 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java
@@ -50,6 +50,8 @@ import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
import at.gv.egiz.eaaf.core.impl.idp.auth.modules.ModuleRegistration;
import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl;
import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl;
+import at.gv.egiz.eaaf.core.impl.idp.validation.IHttpRequestValidator;
+import at.gv.egiz.eaaf.core.impl.idp.validation.NothingHttpRequestValidatior;
import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.ServletException;
@@ -80,6 +82,8 @@ public abstract class AbstractAuthenticationManager implements IAuthenticationMa
protected IRevisionLogger revisionsLogger;
@Autowired(required = false)
protected ISsoManager ssoManager;
+ @Autowired(required = false)
+ protected IHttpRequestValidator httpRequestValidator = new NothingHttpRequestValidatior();
ModuleRegistration moduleRegistration;
@@ -146,6 +150,9 @@ public abstract class AbstractAuthenticationManager implements IAuthenticationMa
throw new NoPassivAuthenticationException();
}
+ // inject additional security information
+ httpRequestValidator.setValidationInfos(httpResp, pendingReq);
+
// check Single Sign-On functionality if SSOManager is available
boolean isValidSsoSession = false;
if (ssoManager != null) {
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java
index 41d15743..49aa2b35 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java
@@ -40,11 +40,14 @@ import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger;
import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage;
import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy;
import at.gv.egiz.eaaf.core.exceptions.EaafException;
+import at.gv.egiz.eaaf.core.exceptions.EaafSecurityException;
import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException;
import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException;
import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
import at.gv.egiz.eaaf.core.impl.data.ExceptionContainer;
import at.gv.egiz.eaaf.core.impl.data.Pair;
+import at.gv.egiz.eaaf.core.impl.idp.validation.IHttpRequestValidator;
+import at.gv.egiz.eaaf.core.impl.idp.validation.NothingHttpRequestValidatior;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -75,6 +78,9 @@ public abstract class AbstractController {
@Autowired
protected IPendingRequestIdGenerationStrategy reqIdGenerationStrategy;
+ @Autowired(required = false)
+ protected IHttpRequestValidator httpRequestValidator = new NothingHttpRequestValidatior();
+
/**
* EAAF framework exception handler.
*
@@ -145,6 +151,19 @@ public abstract class AbstractController {
}
+ /**
+ * Extension point to implement additional request validation.
+ *
+ * @param httpReq Current HTTP request
+ * @param pendingReq Current pending-request selected by pendingRequestId
+ * @throws EaafSecurityException In case of a validation error
+ */
+ protected void extendedRequestValidation(@Nonnull final HttpServletRequest httpReq,
+ @Nonnull final IRequest pendingReq) throws EaafSecurityException {
+ httpRequestValidator.validate(httpReq, pendingReq);
+
+ }
+
protected void handleError(final String errorMessage, final Throwable exceptionThrown,
final HttpServletRequest req, final HttpServletResponse resp, IRequest pendingReq)
throws IOException, EaafException {
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java
index 46de6167..17d240cb 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java
@@ -22,8 +22,6 @@ package at.gv.egiz.eaaf.core.impl.idp.controller;
import java.io.IOException;
import org.apache.commons.text.StringEscapeUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import at.gv.egiz.eaaf.core.api.IRequest;
@@ -37,16 +35,17 @@ import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException;
import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
/**
* Servlet that resumes a suspended process (in case of asynchronous tasks).
*
* @author tknall
+ * @author tlenz
*
*/
+@Slf4j
public abstract class AbstractProcessEngineSignalController extends AbstractController {
- private static final Logger log =
- LoggerFactory.getLogger(AbstractProcessEngineSignalController.class);
@Autowired(required = true)
protected ProcessEngine processEngine;
@@ -75,6 +74,9 @@ public abstract class AbstractProcessEngineSignalController extends AbstractCont
// change pending-request ID
requestStorage.changePendingRequestID(pendingReq);
+ // extended validation of in-comming HTTP requests
+ extendedRequestValidation(req, pendingReq);
+
// process instance is mandatory
if (pendingReq.getProcessInstanceId() == null) {
throw new EaafIllegalStateException(
@@ -99,6 +101,8 @@ public abstract class AbstractProcessEngineSignalController extends AbstractCont
}
+
+
/**
* Retrieves the current pending-request id from the HttpServletRequest
* parameter
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java
index a52d2fda..6f8790b2 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java
@@ -237,6 +237,9 @@ public class ProtocolFinalizationController extends AbstractController {
//set MDC variables
TransactionIdUtils.setAllLoggingVariables(pendingReq);
+ // extended validation of in-comming HTTP requests
+ extendedRequestValidation(req, pendingReq);
+
//perform protocol finalization steps
protAuthService.finalizeAuthentication(req, resp, pendingReq);
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/CookieBasedRequestValidator.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/CookieBasedRequestValidator.java
new file mode 100644
index 00000000..98da0c46
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/CookieBasedRequestValidator.java
@@ -0,0 +1,77 @@
+package at.gv.egiz.eaaf.core.impl.idp.validation;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.util.WebUtils;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.exceptions.EaafSecurityException;
+import at.gv.egiz.eaaf.core.exceptions.EaafStorageException;
+import jakarta.annotation.Nonnull;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Validate incoming requests based in HTTP cookies.
+ */
+@Slf4j
+public class CookieBasedRequestValidator implements IHttpRequestValidator {
+
+ public static final String HTTP_COOKIE_SEC = "eaafSession";
+
+ @Override
+ public void setValidationInfos(@Nonnull final HttpServletResponse httpResponse,
+ @Nonnull final IRequest pendingReq) throws EaafSecurityException {
+ try {
+ log.debug("Injecting authentication-process HTTP cookie ... ");
+ String authProcessIdentifier = UUID.randomUUID().toString();
+ httpResponse.addCookie(generatePendingRequestIdCookie(authProcessIdentifier, pendingReq));
+ pendingReq.setRawDataToTransaction(HTTP_COOKIE_SEC, authProcessIdentifier);
+
+ } catch (MalformedURLException | EaafStorageException e) {
+ throw new EaafSecurityException("process.81", e);
+ }
+
+ }
+
+ @Override
+ public void validate(@Nonnull final HttpServletRequest httpReq,
+ @Nonnull final IRequest pendingReq) throws EaafSecurityException {
+ String storedAuthProcessIdentifier = pendingReq.getRawData(HTTP_COOKIE_SEC, String.class);
+
+ if (StringUtils.isNotEmpty(storedAuthProcessIdentifier)) {
+ Cookie authProcessIdentifier = WebUtils.getCookie(httpReq, HTTP_COOKIE_SEC);
+ if (storedAuthProcessIdentifier.equals(authProcessIdentifier.getValue())) {
+ log.trace("Stored authentication-process HTTP cookie matches. Resume process ... ");
+
+ } else {
+ log.info("Stored authentication-process-Id:{} does not match to Id from HTTP cookie:{}",
+ storedAuthProcessIdentifier, authProcessIdentifier);
+ throw new EaafSecurityException("process.80");
+
+ }
+
+ } else {
+ log.debug("No stored authentication-process HTTP cookie. Skipping validation ... ");
+
+ }
+ }
+
+ private Cookie generatePendingRequestIdCookie(final String authProcessIdentifier, final IRequest pendingReq)
+ throws MalformedURLException {
+ Cookie cookie = new Cookie(
+ HTTP_COOKIE_SEC, authProcessIdentifier);
+ cookie.setHttpOnly(true);
+ cookie.setSecure(true);
+ URL url = new URL(pendingReq.getAuthUrlWithOutSlash());
+ cookie.setPath(url.getPath());
+ return cookie;
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/IHttpRequestValidator.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/IHttpRequestValidator.java
new file mode 100644
index 00000000..5c5e0ba1
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/IHttpRequestValidator.java
@@ -0,0 +1,31 @@
+package at.gv.egiz.eaaf.core.impl.idp.validation;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.exceptions.EaafSecurityException;
+import jakarta.annotation.Nonnull;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+public interface IHttpRequestValidator {
+
+ /**
+ * Set validation information to current process.
+ *
+ * @param httpResponse Current HTTP response
+ * @param pendingReq Current pendingRequest
+ * @throws EaafSecurityException In case of an internal processing error
+ */
+ void setValidationInfos(@Nonnull final HttpServletResponse httpResponse,
+ @Nonnull final IRequest pendingReq) throws EaafSecurityException;
+
+ /**
+ * Validate incoming HTTP requests.
+ *
+ * @param httpReq Current HTTP request
+ * @param pendingReq Current pending-request selected by pendingRequestId
+ * @throws EaafSecurityException In case of a validation error
+ */
+ void validate(HttpServletRequest httpReq,
+ IRequest pendingReq) throws EaafSecurityException;
+
+} \ No newline at end of file
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/NothingHttpRequestValidatior.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/NothingHttpRequestValidatior.java
new file mode 100644
index 00000000..073dbbd0
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/NothingHttpRequestValidatior.java
@@ -0,0 +1,28 @@
+package at.gv.egiz.eaaf.core.impl.idp.validation;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.exceptions.EaafSecurityException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Blank HTTP request validation with no validation.
+ */
+@Slf4j
+public class NothingHttpRequestValidatior implements IHttpRequestValidator {
+
+ @Override
+ public void validate(HttpServletRequest httpReq, IRequest pendingReq)
+ throws EaafSecurityException {
+ log.trace("Extended HTTP request validation is not implemented ... ");
+
+ }
+
+ @Override
+ public void setValidationInfos(HttpServletResponse httpResponse, IRequest pendingReq)
+ throws EaafSecurityException {
+
+ }
+
+}
diff --git a/eaaf_core/src/main/resources/messages/eaaf_core_messages.properties b/eaaf_core/src/main/resources/messages/eaaf_core_messages.properties
index c5cb1bb1..a88523f2 100644
--- a/eaaf_core/src/main/resources/messages/eaaf_core_messages.properties
+++ b/eaaf_core/src/main/resources/messages/eaaf_core_messages.properties
@@ -13,6 +13,9 @@ process.02=Find no applicable authentication process for current state or user-s
process.03=Can not resume the authentication process. Reason: {0}
process.04=Can not execute authentication process. Problem with an internal state
+process.80=Can not continue authentication process, because HTTP security validation failed.
+process.81=Can not continue authentication process, because can initialize security context.
+
process.90=Forward to service-provider not possible, because it's not supported.
process.98=Not supported internal state. Reason: {0}
diff --git a/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/test/impl/idp/validation/CookieBasedRequestValidatorTest.java b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/test/impl/idp/validation/CookieBasedRequestValidatorTest.java
new file mode 100644
index 00000000..9e02fc91
--- /dev/null
+++ b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/test/impl/idp/validation/CookieBasedRequestValidatorTest.java
@@ -0,0 +1,115 @@
+package at.gv.egiz.eaaf.core.test.impl.idp.validation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.exceptions.EaafSecurityException;
+import at.gv.egiz.eaaf.core.impl.idp.auth.dummy.DummyPendingRequest;
+import at.gv.egiz.eaaf.core.impl.idp.validation.CookieBasedRequestValidator;
+import at.gv.egiz.eaaf.core.test.dummy.DummyAuthConfigMap;
+import jakarta.servlet.http.Cookie;
+import lombok.SneakyThrows;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("/SpringTest-context_eaaf_core.xml")
+public class CookieBasedRequestValidatorTest {
+
+ DummyAuthConfigMap config = new DummyAuthConfigMap();
+
+ CookieBasedRequestValidator toCheck = new CookieBasedRequestValidator();
+ MockHttpServletRequest httpReq;
+ IRequest pendingReq;
+
+ /**
+ * jUnit test initializer.
+ */
+ @Before
+ @SneakyThrows
+ public void initialize() {
+ pendingReq = new DummyPendingRequest();
+
+ httpReq = new MockHttpServletRequest("POST", "https://localhost/authhandler");
+ ((DummyPendingRequest) pendingReq).initialize(httpReq, config);
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void setHttpCookie() {
+ MockHttpServletResponse httpResp = new MockHttpServletResponse();
+ toCheck.setValidationInfos(httpResp, pendingReq);
+
+ // validate state
+ String storedCookie = pendingReq.getRawData(CookieBasedRequestValidator.HTTP_COOKIE_SEC, String.class);
+ assertNotNull("stored http cookie", storedCookie);
+
+ Cookie cookie = httpResp.getCookie(CookieBasedRequestValidator.HTTP_COOKIE_SEC);
+ assertNotNull("response http cookie", cookie);
+
+ assertEquals(storedCookie, cookie.getValue(), "cookie value not match");
+
+ assertTrue("httpOnly", cookie.isHttpOnly());
+ assertTrue("secured", cookie.getSecure());
+
+ assertEquals("", cookie.getPath(), "wrong Context Path");
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void success() {
+ MockHttpServletResponse httpResp = new MockHttpServletResponse();
+ toCheck.setValidationInfos(httpResp, pendingReq);
+
+ // validate state
+ httpReq.setCookies(httpResp.getCookies());
+ toCheck.validate(httpReq, pendingReq);
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void notCookieInSession() {
+ MockHttpServletResponse httpResp = new MockHttpServletResponse();
+ toCheck.setValidationInfos(httpResp, pendingReq);
+
+ // validate state
+ pendingReq.removeRawDataFromTransaction(CookieBasedRequestValidator.HTTP_COOKIE_SEC);
+
+ httpReq.setCookies(httpResp.getCookies());
+ toCheck.validate(httpReq, pendingReq);
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void wrongCookie() {
+ MockHttpServletResponse httpResp = new MockHttpServletResponse();
+ toCheck.setValidationInfos(httpResp, pendingReq);
+
+ // validate state
+
+ Cookie cookie = httpResp.getCookie(CookieBasedRequestValidator.HTTP_COOKIE_SEC);
+ cookie.setValue(UUID.randomUUID().toString());
+ httpReq.setCookies(cookie);
+
+ EaafSecurityException error = assertThrows(EaafSecurityException.class,
+ () -> toCheck.validate(httpReq, pendingReq));
+ assertEquals("process.80", error.getErrorId(), "wrong ErrorCode");
+
+ }
+
+}