diff options
Diffstat (limited to 'eaaf_core')
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"); + +  } + +} | 
