summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cisettings.xml5
-rw-r--r--.gitlab-ci.yml21
-rw-r--r--build_reporting/pom.xml2
-rw-r--r--eaaf-springboot-utils/pom.xml2
-rw-r--r--eaaf_core/pom.xml9
-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/auth/services/ProtocolAuthenticationService.java6
-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/controller/protocols/RequestImpl.java79
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/CookieBasedRequestValidator.java82
-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/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java69
-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
-rw-r--r--eaaf_core_api/pom.xml2
-rw-r--r--eaaf_core_api/src/main/java/at/gv/egiz/eaaf/core/exceptions/EaafSecurityException.java43
-rw-r--r--eaaf_core_utils/pom.xml2
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java37
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java47
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java169
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java77
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/ConfigurationUtils.java45
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Random.java2
-rw-r--r--eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java306
-rw-r--r--eaaf_core_utils/src/test/resources/data/config1.properties4
-rw-r--r--eaaf_modules/eaaf_module_auth_sl20/pom.xml2
-rw-r--r--eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java2
-rw-r--r--eaaf_modules/eaaf_module_auth_sl20/src/test/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/AbstractJsonSecurityUtilsTest.java6
-rw-r--r--eaaf_modules/eaaf_module_moa-sig/pom.xml2
-rw-r--r--eaaf_modules/eaaf_module_pvp2_core/pom.xml2
-rw-r--r--eaaf_modules/eaaf_module_pvp2_idp/pom.xml2
-rw-r--r--eaaf_modules/eaaf_module_pvp2_sp/pom.xml2
-rw-r--r--eaaf_modules/pom.xml2
-rw-r--r--pom.xml41
37 files changed, 1126 insertions, 162 deletions
diff --git a/.cisettings.xml b/.cisettings.xml
index 8556c6a8..5676268c 100644
--- a/.cisettings.xml
+++ b/.cisettings.xml
@@ -27,11 +27,6 @@
<id>egizMaven</id>
<username>${env.EGIZ_MAVEN_USER}</username>
<password>${env.EGIZ_MAVEN_PASSWORD}</password>
- <configuration>
- <knownHostsProvider implementation="org.apache.maven.wagon.providers.ssh.knownhost.NullKnownHostProvider">
- <hostKeyChecking>no</hostKeyChecking>
- </knownHostsProvider>
- </configuration>
</server>
</servers>
</settings>
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 01bdd6b9..f6ebe486 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,7 +7,6 @@ variables:
LIB_NAME: "eaaf-components"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true --settings ${CI_PROJECT_DIR}/.cisettings.xml"
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=${CI_PROJECT_DIR}/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
- DEPLOY_EGIZ: "apps.egiz.gv.at ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFyPUwab/Ipmc9NxI+c2i0fzLcIilh3uNebUyUYKFm04bW+aGHuwtsmAka5T3amQA0ADVMTGPWk1JGW0D0dgvXVcU8yYKYw61imKHBiPIdRNh9zVV/p5iL6bCK3AZ0UoZFfeIRNWNfKA6XGJ5e9i2DD1H8BllFOsWeUUYA8GA4tN2j8vL/dZnMcYmbisaUPuCjd7TGnbp+CcRO/f9w87qU/vp2fTG+sNBApuSR18BinUerSK9YK5qLgHxmVeIDtALq1JD5OGacvnTfAczfwSOf37+jtRzpMicGQOBQztoqfifPDAKrr6tYPrQ/pS4PKM/MnhYlvSwQa5h046REFXrx"
GIT_SUBMODULE_STRATEGY: recursive
GIT_DEPTH: "2"
SECURE_LOG_LEVEL: "debug"
@@ -23,12 +22,12 @@ dependency_scanning:
variables:
MAVEN_CLI_OPTS: "-DskipTests --settings ${CI_PROJECT_DIR}/.cisettings.xml"
DS_JAVA_VERSION: 17
-
+
spotbugs-sast:
variables:
- MAVEN_CLI_OPTS: "-DskipTests --settings ${CI_PROJECT_DIR}/.cisettings.xml"
- SAST_JAVA_VERSION: 17
-
+ MAVEN_CLI_OPTS: "-DskipTests --settings ${CI_PROJECT_DIR}/.cisettings.xml"
+ SAST_JAVA_VERSION: 17
+
default:
tags:
- docker
@@ -42,7 +41,7 @@ stages:
cache:
paths:
- ".m2/repository"
-
+
assemble:
stage: assemble
except:
@@ -58,7 +57,7 @@ assemble:
paths:
- build_reporting/target/site/jacoco-aggregate-ut/jacoco.xml
- build_reporting/target/site/jacoco-aggregate-ut/jacoco.csv
-
+
coverage:
stage: test
image: haynes/jacoco2cobertura:1.0.4
@@ -71,7 +70,7 @@ coverage:
# read the <source></source> tag and prepend the path to every filename attribute
#- 'python /opt/source2filename.py target/site/cobertura.xml'
- awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print 100*covered/instructions, "% covered" }' $JACOCO_CSV_LOCATION
- needs:
+ needs:
- job: assemble
dependencies:
- assemble
@@ -80,7 +79,7 @@ coverage:
coverage_report:
coverage_format: cobertura
path: target/site/cobertura.xml
-
+
publishToGitlab:
stage: package
tags:
@@ -92,11 +91,11 @@ publishToGitlab:
- mkdir -p ~/.ssh
#- ssh-keyscan apps.egiz.gv.at >> ~/.ssh/known_hosts
- echo $DEPLOY_EGIZ >> ~/.ssh/known_hosts
- - chmod 644 ~/.ssh/known_hosts
+ - chmod 644 ~/.ssh/known_hosts
script: |
export VERSION=$(mvn -B help:evaluate -Dexpression=project.version -B | grep -v "\[INFO\]" | grep -Po "\d+\.\d+\.\d+((-\w*)+)?")
echo "Publishing version $VERSION for $LIB_NAME to public EGIZ maven"
- mvn $MAVEN_CLI_OPTS deploy -s .cisettings.xml -P jenkinsDeploy -DskipTests
+ mvn $MAVEN_CLI_OPTS deploy -s .cisettings.xml -P jenkinsDeploy -DskipTests
echo "VERSION=$VERSION" >> variables.env
artifacts:
when: always
diff --git a/build_reporting/pom.xml b/build_reporting/pom.xml
index 05f089f7..0de30d7b 100644
--- a/build_reporting/pom.xml
+++ b/build_reporting/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<artifactId>eaaf_build_reporting</artifactId>
<packaging>pom</packaging>
diff --git a/eaaf-springboot-utils/pom.xml b/eaaf-springboot-utils/pom.xml
index 3c92b428..ce289bd7 100644
--- a/eaaf-springboot-utils/pom.xml
+++ b/eaaf-springboot-utils/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf-springboot-utils</artifactId>
diff --git a/eaaf_core/pom.xml b/eaaf_core/pom.xml
index e93e4d38..df2530f2 100644
--- a/eaaf_core/pom.xml
+++ b/eaaf_core/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<groupId>at.gv.egiz.eaaf</groupId>
@@ -66,7 +66,12 @@
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<scope>provided</scope>
- </dependency>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-joda</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
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/auth/services/ProtocolAuthenticationService.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java
index b1f45801..65041c92 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java
@@ -593,18 +593,18 @@ public class ProtocolAuthenticationService implements IProtocolAuthenticationSer
// write error message
writeHtmlErrorResponse(req, resp, e.getMessage(), internalErrorCode,
e instanceof EaafException ? ((EaafException) e).getParams() : null,
- statusMessager.mapInternalErrorToExternalError(internalErrorCode), errorData);
+ errorTicketService.getExternalCodeFromInternal(internalErrorCode), errorData);
} else if (e instanceof EaafException) {
// send HTML formated error message
writeHtmlErrorResponse(req, resp, e.getMessage(), internalErrorCode, ((EaafException) e).getParams(),
- statusMessager.mapInternalErrorToExternalError(internalErrorCode), errorData);
+ errorTicketService.getExternalCodeFromInternal(internalErrorCode), errorData);
} else {
// write generic message for general exceptions
final String msg = statusMessager.getMessage(IStatusMessenger.CODES_INTERNAL_ERROR_GENERIC, null);
writeHtmlErrorResponse(req, resp, msg, internalErrorCode, null,
- statusMessager.mapInternalErrorToExternalError(internalErrorCode), errorData);
+ errorTicketService.getExternalCodeFromInternal(internalErrorCode), errorData);
}
}
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/controller/protocols/RequestImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java
index f5703cab..ee4f2aaf 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java
@@ -109,7 +109,7 @@ public abstract class RequestImpl implements IRequest, Serializable {
private boolean needUserConsent = false;
private boolean currentlyInIframe = false;
-
+
private final Map<String, Object> genericDataStorage = new HashMap<>();
/**
@@ -129,9 +129,10 @@ public abstract class RequestImpl implements IRequest, Serializable {
/**
* Initialize this pendingRequest object.
*
- * @param req {@link HttpServletRequest}
- * @param authConfig {@link IConfiguration}
- * @param transactionId Unique ID for technical log correlation that should be used in this pendingRequest
+ * @param req {@link HttpServletRequest}
+ * @param authConfig {@link IConfiguration}
+ * @param transactionId Unique ID for technical log correlation that should be
+ * used in this pendingRequest
* @throws EaafException
*
*/
@@ -144,11 +145,13 @@ public abstract class RequestImpl implements IRequest, Serializable {
/**
* Initialize this pendingRequest object.
*
- * @param req {@link HttpServletRequest}
- * @param authConfig {@link IConfiguration}
- * @param transactionId Unique ID for technical log correlation that should be used in this pendingRequest
- * @param piiTransactionId Unique ID for PII data correlation that should be used in this pendingRequest
- * for logging. If 'null' a new one will be generated
+ * @param req {@link HttpServletRequest}
+ * @param authConfig {@link IConfiguration}
+ * @param transactionId Unique ID for technical log correlation that should
+ * be used in this pendingRequest
+ * @param piiTransactionId Unique ID for PII data correlation that should be
+ * used in this pendingRequest for logging. If 'null' a
+ * new one will be generated
*
* @throws EaafException
*
@@ -235,7 +238,6 @@ public abstract class RequestImpl implements IRequest, Serializable {
this.passiv = passiv;
}
-
public final void setForce(final boolean force) {
this.force = force;
}
@@ -335,7 +337,7 @@ public abstract class RequestImpl implements IRequest, Serializable {
/**
* Inject Service-Provider configuration into that authentication process.
- *
+ *
* @param spConfig SP configuration
*/
@JsonIgnore
@@ -370,8 +372,8 @@ public abstract class RequestImpl implements IRequest, Serializable {
}
/**
- * Set an unique transaction identifier to correlate technical logging
- * in one single transaction.
+ * Set an unique transaction identifier to correlate technical logging in one
+ * single transaction.
*
* @param id Unique identifier
*/
@@ -381,8 +383,8 @@ public abstract class RequestImpl implements IRequest, Serializable {
}
/**
- * Set an unique session identifier to correlate technical logging over a set of transactions,
- * like SSO as one example.
+ * Set an unique session identifier to correlate technical logging over a set of
+ * transactions, like SSO as one example.
*
* @param id Unique identifier
*/
@@ -394,7 +396,9 @@ public abstract class RequestImpl implements IRequest, Serializable {
/**
* Set an unique transaction identifier to correlate PII related data.
*
- * <p>This identifier will be not used for technical logging.</p>
+ * <p>
+ * This identifier will be not used for technical logging.
+ * </p>
*
* @param id Unique identifier
*/
@@ -403,7 +407,6 @@ public abstract class RequestImpl implements IRequest, Serializable {
}
-
public void setProcessInstanceId(final String id) {
this.processInstanceId = id;
@@ -488,44 +491,35 @@ public abstract class RequestImpl implements IRequest, Serializable {
@Override
public void setProcessInFrame(boolean flag) {
this.currentlyInIframe = flag;
-
- }
-
- @Override
- public final Object getRawData(final String key) {
- if (StringUtils.isNotEmpty(key)) {
- return objectSaveJsonDeserialization(genericDataStorage.get(key));
- }
-
- log.info("Can not load generic request-data with key='null'");
- return null;
}
@Override
- public final <T> T getRawData(final String key, final Class<T> clazz) {
+ public final Object getRawData(final String key) {
if (StringUtils.isNotEmpty(key)) {
final Object data = genericDataStorage.get(key);
-
if (data == null) {
return null;
}
+ return objectSaveJsonDeserialization(genericDataStorage.get(key));
- try {
- Object deserializedObject = objectSaveJsonDeserialization(data);
- return deserializedObject != null ? (T) deserializedObject : null;
-
- } catch (final Exception e) {
- log.warn("Generic request-data object can not be casted to requested type", e);
- return null;
-
- }
}
log.info("Can not load generic request-data with key='null'");
return null;
+ }
+
+ @Override
+ public final <T> T getRawData(final String key, final Class<T> clazz) {
+ try {
+ final Object deserializedObject = getRawData(key);
+ return deserializedObject != null ? (T) deserializedObject : null;
+ } catch (final Exception e) {
+ log.warn("Generic request-data object can not be casted to requested type", e);
+ return null;
+ }
}
@Override
@@ -572,6 +566,7 @@ public abstract class RequestImpl implements IRequest, Serializable {
}
+ @Override
public final void removeRawDataFromTransaction(String key) {
genericDataStorage.remove(key);
@@ -584,7 +579,7 @@ public abstract class RequestImpl implements IRequest, Serializable {
.clazzzType(object.getClass().getName())
.build());
- } catch (EaafJsonMapperException e) {
+ } catch (final EaafJsonMapperException e) {
throw new EaafStorageException("Can no serialize object to JSON", e);
}
@@ -593,9 +588,9 @@ public abstract class RequestImpl implements IRequest, Serializable {
private Object objectSaveJsonDeserialization(Object data) {
try {
if (data instanceof String) {
- RawDataHolder holder = (RawDataHolder) DefaultJsonMapper.deserialize(
+ final RawDataHolder holder = (RawDataHolder) DefaultJsonMapper.deserialize(
(String) data, RawDataHolder.class);
- Class<?> clz = Class.forName(holder.getClazzzType());
+ final Class<?> clz = Class.forName(holder.getClazzzType());
return DefaultJsonMapper.deserialize(holder.getObject(), clz);
} else {
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..7fd2a910
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/validation/CookieBasedRequestValidator.java
@@ -0,0 +1,82 @@
+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";
+ public static final String COOKIE_SAME_SITE_ATTR = "SameSite";
+
+ @Override
+ public void setValidationInfos(@Nonnull final HttpServletResponse httpResponse,
+ @Nonnull final IRequest pendingReq) throws EaafSecurityException {
+ try {
+ log.debug("Injecting authentication-process HTTP cookie ... ");
+ String storedAuthProcessIdentifier = pendingReq.getRawData(HTTP_COOKIE_SEC, String.class);
+ String authProcessIdentifier = StringUtils.isNotEmpty(storedAuthProcessIdentifier)
+ ? storedAuthProcessIdentifier
+ : 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 (authProcessIdentifier != null
+ && 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 != null ? authProcessIdentifier.getValue() : " ---no cookie---");
+ 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);
+ cookie.setPath(new URL(pendingReq.getAuthUrlWithOutSlash()).getPath());
+ cookie.setAttribute(COOKIE_SAME_SITE_ATTR, "None");
+ 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/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java
index 8303e860..b6586bff 100644
--- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java
@@ -1,6 +1,8 @@
package at.gv.egiz.eaaf.core.impl.utils;
import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -9,10 +11,10 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import at.gv.egiz.eaaf.core.exceptions.EaafJsonMapperException;
import lombok.Getter;
@@ -27,6 +29,11 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class DefaultJsonMapper {
+ private static final String JAVA_TIME_MODULE_CLASS = "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule";
+ private static final String JAVA_TIME_MODULE_CONSTRUCTOR = "JavaTimeModule";
+ private static final String JODA_TIME_MODULE_CLASS = "com.fasterxml.jackson.datatype.joda.JodaModule";
+ private static final String JODA_TIME_MODULE_CONSTRUCTOR = "JodaModule";
+
@Getter
private static final ObjectMapper jsonMapper = new ObjectMapper();
@@ -39,7 +46,9 @@ public final class DefaultJsonMapper {
jsonMapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
jsonMapper.setVisibility(PropertyAccessor.IS_GETTER, Visibility.PUBLIC_ONLY);
- jsonMapper.registerModule(new JavaTimeModule());
+ registerModuleIfAvailable(JAVA_TIME_MODULE_CLASS, JAVA_TIME_MODULE_CONSTRUCTOR);
+ registerModuleIfAvailable(JODA_TIME_MODULE_CLASS, JODA_TIME_MODULE_CONSTRUCTOR);
+
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
@@ -101,6 +110,62 @@ public final class DefaultJsonMapper {
throw new EaafJsonMapperException(e.getMessage(), e);
}
+ }
+
+ private static void registerModuleIfAvailable(String fullModuleClasspath, String constructorName) {
+ Module module = checkAndLoadModule(fullModuleClasspath, constructorName);
+ if (module != null) {
+ jsonMapper.registerModule(module);
+ log.info("Register Jackson module: {}", module.getModuleName());
+
+ } else {
+ log.debug("Skipping Jackson module not on classpath: {}", fullModuleClasspath);
+
+ }
+ }
+
+ private static Module checkAndLoadModule(String fullModuleClasspath, String constructorName) {
+ Class<?> clazz = getModuleClass(fullModuleClasspath);
+ if (clazz != null) {
+ return getModuleInstance(clazz, constructorName);
+ }
+
+ return null;
+ }
+
+ private static Module getModuleInstance(Class<?> clazz, String constructorName) {
+ try {
+
+ final Constructor<?> constructor = clazz.getConstructor(new Class[] {});
+ if (constructor != null) {
+ log.trace("Found method: {} for class:{}. Creating instanze .... ",
+ constructorName, clazz.getName());
+ final Object rawModule = constructor.newInstance(null);
+ return rawModule instanceof Module ? (Module) rawModule : null;
+
+ } else {
+ log.warn("Find module:{}, but Constructor:{} looks wrong!", clazz.getName(), constructorName);
+
+ }
+
+ } catch (NoSuchMethodException | SecurityException | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
+ log.info("Can not create instanze of Jackson module: {}. Reason: {}", clazz.getName(), e.getMessage());
+ log.trace("Detailed error", e);
+
+ }
+ return null;
+
+ }
+ private static Class<?> getModuleClass(String fullClassName) {
+ try {
+ return Class.forName(fullClassName);
+
+ } catch (final ClassNotFoundException e1) {
+ log.debug("No {} implemenation in ClassPath. Jackson module will not be available", fullClassName);
+ return null;
+
+ }
}
}
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");
+
+ }
+
+}
diff --git a/eaaf_core_api/pom.xml b/eaaf_core_api/pom.xml
index d9910044..9a23b0d5 100644
--- a/eaaf_core_api/pom.xml
+++ b/eaaf_core_api/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_core_api</artifactId>
diff --git a/eaaf_core_api/src/main/java/at/gv/egiz/eaaf/core/exceptions/EaafSecurityException.java b/eaaf_core_api/src/main/java/at/gv/egiz/eaaf/core/exceptions/EaafSecurityException.java
new file mode 100644
index 00000000..1a3dd67e
--- /dev/null
+++ b/eaaf_core_api/src/main/java/at/gv/egiz/eaaf/core/exceptions/EaafSecurityException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a
+ * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology.
+ *
+ * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European
+ * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in
+ * compliance with the Licence. You may obtain a copy of the Licence at:
+ * https://joinup.ec.europa.eu/news/understanding-eupl-v12
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the Licence
+ * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the Licence for the specific language governing permissions and limitations under
+ * the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text file for details on the
+ * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative
+ * works that you distribute must include a readable copy of the "NOTICE" text file.
+*/
+
+package at.gv.egiz.eaaf.core.exceptions;
+
+/**
+ * In case of a security-validation error.
+ */
+public class EaafSecurityException extends EaafException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EaafSecurityException(final String msg) {
+ super(msg, null);
+
+ }
+
+ public EaafSecurityException(final String msg, final Object[] params) {
+ super(msg, params);
+
+ }
+
+ public EaafSecurityException(String msg, Exception e) {
+ super(msg, null, e);
+ }
+
+}
diff --git a/eaaf_core_utils/pom.xml b/eaaf_core_utils/pom.xml
index 8d8bd116..c6bcba44 100644
--- a/eaaf_core_utils/pom.xml
+++ b/eaaf_core_utils/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_core_utils</artifactId>
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java
index 026b76c4..6ea13905 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java
@@ -7,11 +7,15 @@ import javax.net.ssl.SSLException;
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.util.TimeValue;
public class EaafHttpRequestRetryHandler extends DefaultHttpRequestRetryStrategy implements
HttpRequestRetryStrategy {
+ private boolean allowHttpPostRetry;
+
/**
* Create the request retry handler using the following list of non-retriable.
* IOException classes: <br>
@@ -25,15 +29,48 @@ public class EaafHttpRequestRetryHandler extends DefaultHttpRequestRetryStrategy
* <li>502</li>
* </ul>
* After two seconds if no {@code Retry-After} header was set.
+ * <p>
+ * Do not allow retry of HTTP POST
+ * </p>
*
* @param retryCount how many times to retry; 0 means no retries
*/
public EaafHttpRequestRetryHandler(final int retryCount) {
+ this(retryCount, false);
+
+ }
+
+ /**
+ * Create the request retry handler using the following list of non-retriable.
+ * IOException classes: <br>
+ * <ul>
+ * <li>UnknownHostException</li>
+ * <li>SSLException</li>
+ * </ul>
+ * HTTP StatusCodes:
+ * <ul>
+ * <li>429</li>
+ * <li>502</li>
+ * </ul>
+ * After two seconds if no {@code Retry-After} header was set.
+ *
+ * @param retryCount how many times to retry; 0 means no retries
+ * @param retryHttpPost In case of <code>true</code> allow retry of HTTP POST
+ * too
+ */
+ public EaafHttpRequestRetryHandler(final int retryCount, final boolean retryHttpPost) {
super(retryCount, TimeValue.ofSeconds(2),
Arrays.asList(
UnknownHostException.class,
SSLException.class),
Arrays.asList(429, 502));
+ allowHttpPostRetry = retryHttpPost;
+
+ }
+
+ protected boolean handleAsIdempotent(final HttpRequest request) {
+ return allowHttpPostRetry && Method.POST.isSame(request.getMethod())
+ || Method.isIdempotent(request.getMethod());
}
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java
index 4d808f2b..5ecbe1ec 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java
@@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
public class HttpClientConfiguration {
private static final String MSG_KEYSTORE_NAME = "KeyStore for httpClient: {0}";
+ private static final String MSG_TRUSTSTORE_NAME = "TrustStore for httpClient: {0}";
private static final String ERROR_00 = "internal.httpclient.00";
private static final String ERROR_01 = "internal.httpclient.01";
@@ -49,6 +50,9 @@ public class HttpClientConfiguration {
boolean enablePreEmptiveHttpBasicAuth = true;
@Setter
+ boolean enableHttpProxyMode = false;
+
+ @Setter
boolean disableTlsHostCertificateValidation = false;
@Setter
@@ -61,9 +65,15 @@ public class HttpClientConfiguration {
private String sslKeyPassword;
@Setter
+ private KeyStoreConfiguration trustStoreConfig;
+
+ @Setter
private boolean followHttpRedirects = true;
@Setter
+ private boolean httpPostRetryAllowed = false;
+
+ @Setter
private int httpErrorRetryCount = 3;
@Setter
@@ -154,6 +164,14 @@ public class HttpClientConfiguration {
}
}
+ // validate SSL TrustStore if it was set
+ if (this.trustStoreConfig != null) {
+ log.trace("Validating TrustStore: {} for http-client: {} ...",
+ this.trustStoreConfig.getFriendlyName(), this.friendlyName);
+ this.trustStoreConfig.validate();
+
+ }
+
}
/**
@@ -177,6 +195,35 @@ public class HttpClientConfiguration {
}
+ /**
+ * Build a {@link KeyStoreConfiguration} object from trustStore configuration
+ * parameters.
+ *
+ * @param keyStoreType String based KeyStore type
+ * @param keyStorePath Path to KeyStore in case of a software based KeyStore
+ * @param keyStorePassword Password in case of a software based KeyStore
+ * @param keyStoreName Name of the KeyStore in case of a named KeyStore like
+ * HSM-Facade
+ * @throws EaafConfigurationException In case of a configuration error
+ */
+ public void buildTrustStoreConfig(String keyStoreType, String keyStorePath,
+ String keyStorePassword, String keyStoreName) throws EaafConfigurationException {
+ if (StringUtils.isNotEmpty(keyStoreType)) {
+ log.debug("Injecting custom TrustStore configuration for: {} ... ", friendlyName);
+ final KeyStoreConfiguration config = new KeyStoreConfiguration();
+ config.setFriendlyName(MessageFormat.format(MSG_TRUSTSTORE_NAME, friendlyName));
+ config.setKeyStoreType(keyStoreType);
+ config.setSoftKeyStoreFilePath(keyStorePath);
+ config.setSoftKeyStorePassword(keyStorePassword);
+ config.setKeyStoreName(keyStoreName);
+ this.trustStoreConfig = config;
+
+ } else {
+ log.debug("No TrustStoreType. Skipping TrustStore configuration for: {} ... ", friendlyName);
+
+ }
+ }
+
public enum ClientAuthMode {
NONE("none"), PASSWORD("password"), SSL("ssl");
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java
index 5e8edfa3..ceffe26c 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java
@@ -22,6 +22,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.RedirectStrategy;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
@@ -30,14 +31,15 @@ import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
-import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,9 +47,11 @@ import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
import at.gv.egiz.eaaf.core.exceptions.EaafException;
import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory;
+import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration;
import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType;
import at.gv.egiz.eaaf.core.impl.data.Pair;
import at.gv.egiz.eaaf.core.impl.http.interceptor.PreemptiveAuthInterceptor;
+import at.gv.egiz.eaaf.core.impl.utils.ConfigurationUtils;
import jakarta.annotation.Nonnull;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
@@ -64,6 +68,10 @@ public class HttpClientFactory implements IHttpClientFactory {
"client.http.connection.pool.use";
public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL =
"client.http.connection.pool.maxtotal";
+ public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_TIMETOLIFE =
+ "client.http.connection.pool.timetolife";
+ public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_VALIDATION_INACTIVITY =
+ "client.http.connection.pool.validationafterinactivity";
public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE =
"client.http.connection.pool.maxperroute";
public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET =
@@ -74,39 +82,61 @@ public class HttpClientFactory implements IHttpClientFactory {
"client.http.connection.timeout.request";
public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT =
"client.http.connection.retry.count";
- public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL =
- "client.http.ssl.hostnameverifier.trustall";
+ public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST =
+ "client.http.connection.retry.post";
+
+ public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_HOST_SSL =
+ "client.http.connection.proxy.host.ssl";
+ public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_HOST =
+ "client.http.connection.proxy.host";
+ public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_PORT =
+ "client.http.connection.proxy.port";
+ public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_AUTH_USERNAME =
+ "client.http.connection.proxy.auth.username";
+ public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_AUTH_PASSWORD =
+ "client.http.connection.proxy.auth.password";
+
+ public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_ENABLED =
+ "client.http.connection.proxy.enabled";
public static final String PROP_CONFIG_CLIENT_MODE = "client.authmode";
public static final String PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME = "client.auth.http.username";
public static final String PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD = "client.auth.http.password";
public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH =
- "client.auth.ssl.keystore.path";
+ "client.http.auth.ssl.keystore.path";
public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD =
- "client.auth.ssl.keystore.password";
+ "client.http.auth.ssl.keystore.password";
private static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME =
- "client.auth.ssl.keystore.name";
+ "client.http.auth.ssl.keystore.name";
public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE =
- "client.auth.ssl.keystore.type";
+ "client.http.auth.ssl.keystore.type";
public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS =
- "client.auth.ssl.key.alias";
+ "client.http.auth.ssl.key.alias";
public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD =
- "client.auth.ssl.key.password";
+ "client.http.auth.ssl.key.password";
+
+ public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PATH =
+ "client.http.ssl.truststore.path";
+ public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PASSORD =
+ "client.http.ssl.truststore.password";
+ public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_NAME =
+ "client.http.ssl.truststore.name";
+ public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_TYPE =
+ "client.http.ssl.truststore.type";
+ public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL =
+ "client.http.ssl.hostnameverifier.trustall";
// default configuration values
public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "15";
public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "15";
+ public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_VALIDATION_INACTIVITY = "2";
public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "30";
public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "500";
public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100";
public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = "3";
- public static final int DEFAULT_CLEANUP_RUNNER_TIME = 30000;
public static final int DEFAULT_CLEANUP_IDLE_TIME = 60;
- public static final TimeValue CLEANUP_IDLE_TIME =
- TimeValue.of(DEFAULT_CLEANUP_IDLE_TIME, TimeUnit.SECONDS);
-
private String defaultConfigurationId = null;
private final Map<String, Pair<HttpClientBuilder, HttpClientConnectionManager>> availableBuilders =
new HashMap<>();
@@ -148,6 +178,9 @@ public class HttpClientFactory implements IHttpClientFactory {
builder.setDefaultRequestConfig(buildDefaultRequestConfig(config));
injectInternalRetryHandler(builder, config);
+ // inject HTTP Proxy
+ injectHttpProxyConnections(builder, config);
+
// inject basic authentication infos
injectBasicAuthenticationIfRequired(builder, config);
@@ -189,7 +222,8 @@ public class HttpClientFactory implements IHttpClientFactory {
log.info("Set HTTP error-retry to {} for http-client: {}",
config.getHttpErrorRetryCount(), config.getFriendlyName());
builder.setRetryStrategy(new EaafHttpRequestRetryHandler(
- config.getHttpErrorRetryCount()));
+ config.getHttpErrorRetryCount(),
+ config.isHttpPostRetryAllowed()));
} else {
log.info("Disable HTTP error-retry for http-client: {}", config.getFriendlyName());
@@ -211,6 +245,9 @@ public class HttpClientFactory implements IHttpClientFactory {
defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig(defaultHttpClientConfig));
injectInternalRetryHandler(defaultHttpClientBuilder, defaultHttpClientConfig);
+ // inject HTTP Proxy
+ injectHttpProxyConnections(defaultHttpClientBuilder, defaultHttpClientConfig);
+
// inject http basic authentication
injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig);
@@ -229,6 +266,28 @@ public class HttpClientFactory implements IHttpClientFactory {
}
+ private void injectHttpProxyConnections(HttpClientBuilder httpClientBuilder,
+ HttpClientConfiguration httpClientConfig) throws EaafConfigurationException {
+ if (httpClientConfig.isEnableHttpProxyMode()) {
+ log.debug("Injecting HTTP Proxy-Connections ... ");
+ URIScheme proxySchema = basicConfig.getBasicConfigurationBoolean(
+ PROP_CONFIG_CLIENT_HTTP_PROXY_HOST_SSL, false)
+ ? URIScheme.HTTPS
+ : URIScheme.HTTP;
+ HttpHost proxy = new HttpHost(
+ proxySchema.getId(),
+ ConfigurationUtils.parseString(basicConfig, PROP_CONFIG_CLIENT_HTTP_PROXY_HOST),
+ ConfigurationUtils.parseInteger(basicConfig, PROP_CONFIG_CLIENT_HTTP_PROXY_PORT));
+ DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
+ httpClientBuilder.setRoutePlanner(routePlanner);
+ log.info("Set HTTP Proxy-Connection: {} for client: {}", proxy, httpClientConfig.getFriendlyName());
+
+ } else {
+ log.trace("Injection of HTTP Proxy-Connection was skipped");
+
+ }
+ }
+
private HttpClientConfiguration buildDefaultHttpClientConfiguration() throws EaafConfigurationException {
final HttpClientConfiguration config = new HttpClientConfiguration("Default");
@@ -257,12 +316,28 @@ public class HttpClientFactory implements IHttpClientFactory {
config.setSslKeyPassword(
basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD));
+ // set SSL TrustStore if available
+ config.buildTrustStoreConfig(
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_TYPE),
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PATH, StringUtils.EMPTY),
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PASSORD, StringUtils.EMPTY),
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_NAME, StringUtils.EMPTY));
+
config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean(
PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false));
config.setHttpErrorRetryCount(Integer.parseInt(basicConfig.getBasicConfiguration(
PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT,
DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT)));
+ config.setHttpPostRetryAllowed(basicConfig.getBasicConfigurationBoolean(
+ PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST, false));
+
+ config.setEnableHttpProxyMode(basicConfig.getBasicConfigurationBoolean(
+ PROP_CONFIG_CLIENT_HTTP_PROXY_ENABLED, false));
// validate configuration object
config.validate();
@@ -315,11 +390,15 @@ public class HttpClientFactory implements IHttpClientFactory {
log.trace("Injecting SSL client-authentication into http client ... ");
sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore,
httpClientConfig.getSslKeyAlias(), httpClientConfig.getSslKeyPassword(),
+ buildCustomTrustStore(httpClientConfig.getTrustStoreConfig()),
httpClientConfig.isDisableTlsHostCertificateValidation(), httpClientConfig.getFriendlyName());
} else {
log.trace("Initializing default SSL Context ... ");
- sslContext = SSLContexts.createDefault();
+ sslContext = HttpUtils.buildSslContext(
+ buildCustomTrustStore(httpClientConfig.getTrustStoreConfig()),
+ httpClientConfig.isDisableTlsHostCertificateValidation(),
+ httpClientConfig.getFriendlyName());
}
@@ -338,30 +417,55 @@ public class HttpClientFactory implements IHttpClientFactory {
}
+ private KeyStore buildCustomTrustStore(KeyStoreConfiguration trustStoreConfig) throws EaafException {
+ if (trustStoreConfig != null) {
+ log.debug("Open trustStore with type: {}", trustStoreConfig.getKeyStoreType());
+ final Pair<KeyStore, Provider> trustStore = keyStoreFactory.buildNewKeyStore(trustStoreConfig);
+ return trustStore.getFirst();
+
+ } else {
+ return null;
+
+ }
+ }
+
@Nonnull
private HttpClientConnectionManager injectConnectionManager(
- HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) {
+ HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory)
+ throws EaafConfigurationException {
if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE,
true)) {
+
+ final TimeValue timeToLife = TimeValue.of(ConfigurationUtils.parseInteger(
+ basicConfig, PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_TIMETOLIFE, DEFAULT_CLEANUP_IDLE_TIME),
+ TimeUnit.SECONDS);
+ log.debug("Set {} seconds as time-to-life for pooled http connections");
+
final PoolingHttpClientConnectionManager connectionPool = new PoolingHttpClientConnectionManager(
getDefaultRegistry(sslConnectionFactory),
- PoolConcurrencyPolicy.STRICT, CLEANUP_IDLE_TIME, null);
+ PoolConcurrencyPolicy.STRICT, timeToLife, null);
connectionPool.setDefaultMaxPerRoute(Integer.parseInt(
basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE,
DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE)));
connectionPool.setMaxTotal(Integer.parseInt(
basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL,
DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL)));
- connectionPool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(
- Integer.parseInt(
- basicConfig.getBasicConfiguration(
- PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET,
- DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)), TimeUnit.SECONDS).build());
+ connectionPool.setDefaultSocketConfig(SocketConfig.custom()
+ .setSoTimeout(
+ Integer.parseInt(
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET,
+ DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)), TimeUnit.SECONDS)
+ .build());
connectionPool.setDefaultConnectionConfig(ConnectionConfig.custom()
- .setConnectTimeout(
- Long.parseLong(basicConfig.getBasicConfiguration(
+ .setConnectTimeout(Long.parseLong(
+ basicConfig.getBasicConfiguration(
PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION,
DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)), TimeUnit.SECONDS)
+ .setValidateAfterInactivity(Integer.parseInt(
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_VALIDATION_INACTIVITY,
+ DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_VALIDATION_INACTIVITY)), TimeUnit.SECONDS)
.build());
@@ -374,6 +478,23 @@ public class HttpClientFactory implements IHttpClientFactory {
log.debug("Building http-client without Connection-Pool ... ");
final BasicHttpClientConnectionManager basicPool = new BasicHttpClientConnectionManager(
getDefaultRegistry(sslConnectionFactory));
+
+ basicPool.setSocketConfig(SocketConfig.custom()
+ .setSoTimeout(
+ Integer.parseInt(
+ basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET,
+ DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)), TimeUnit.SECONDS)
+ .build());
+ basicPool.setConnectionConfig(ConnectionConfig.custom()
+ .setConnectTimeout(
+ Long.parseLong(basicConfig.getBasicConfiguration(
+ PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION,
+ DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)), TimeUnit.SECONDS)
+ .build());
+
+
+
builder.setConnectionManager(basicPool);
return basicPool;
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java
index d26672f2..61076dfa 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java
@@ -30,8 +30,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.UnrecoverableKeyException;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import org.apache.commons.lang3.StringUtils;
@@ -53,6 +51,8 @@ import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException;
import at.gv.egiz.eaaf.core.impl.data.Pair;
import at.gv.egiz.eaaf.core.impl.data.Triple;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
@@ -258,6 +258,55 @@ public class HttpUtils {
}
/**
+ * Initialize a {@link SSLContext}.
+ *
+ * @param trustAllServerCertificates Deactivate SSL server-certificate
+ * validation
+ * @param friendlyName FriendlyName of the http client for logging
+ * purposes
+ * @return {@link SSLContext} with X509 client authentication
+ * @throws EaafConfigurationException In case of a configuration error
+ * @throws EaafFactoryException In case of a {@link SSLContext}
+ * initialization error
+ */
+ public static SSLContext buildSslContext(
+ boolean trustAllServerCertificates, @Nonnull String friendlyName)
+ throws EaafConfigurationException, EaafFactoryException {
+ return buildSslContext(null, trustAllServerCertificates, friendlyName);
+
+ }
+
+ /**
+ * Initialize a {@link SSLContext}.
+ *
+ * @param trustStore Custom SSL TrustStore, or <code>null</code>
+ * if default truststore should be used
+ * @param trustAllServerCertificates Deactivate SSL server-certificate
+ * validation
+ * @param friendlyName FriendlyName of the http client for logging
+ * purposes
+ * @return {@link SSLContext} with X509 client authentication
+ * @throws EaafConfigurationException In case of a configuration error
+ * @throws EaafFactoryException In case of a {@link SSLContext}
+ * initialization error
+ */
+ public static SSLContext buildSslContext(@Nullable final KeyStore trustStore,
+ boolean trustAllServerCertificates, @Nonnull String friendlyName)
+ throws EaafConfigurationException, EaafFactoryException {
+ try {
+ EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create();
+
+ injectTrustStore(sslContextBuilder, trustStore, trustAllServerCertificates, friendlyName);
+
+ return sslContextBuilder.build();
+
+ } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
+ throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e);
+
+ }
+ }
+
+ /**
* Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client
* authentication.
*
@@ -280,20 +329,8 @@ public class HttpUtils {
@Nullable String keyAlias, @Nullable String keyPasswordString,
boolean trustAllServerCertificates, @Nonnull String friendlyName)
throws EaafConfigurationException, EaafFactoryException {
- try {
- EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create();
-
- injectKeyStore(sslContextBuilder, keyStore, keyAlias, keyPasswordString, friendlyName);
-
- injectTrustStore(sslContextBuilder, null, trustAllServerCertificates, friendlyName);
-
- return sslContextBuilder.build();
-
- } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException
- | KeyStoreException e) {
- throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e);
-
- }
+ return buildSslContextWithSslClientAuthentication(keyStore, keyAlias, keyPasswordString,
+ null, trustAllServerCertificates, friendlyName);
}
/**
@@ -318,7 +355,7 @@ public class HttpUtils {
*/
public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final Pair<KeyStore, Provider> keyStore,
@Nullable String keyAlias, @Nullable String keyPasswordString,
- @Nullable final Pair<KeyStore, Provider> trustStore, boolean trustAllServerCertificates,
+ @Nullable final KeyStore trustStore, boolean trustAllServerCertificates,
@Nonnull String friendlyName)
throws EaafConfigurationException, EaafFactoryException {
try {
@@ -338,7 +375,7 @@ public class HttpUtils {
}
private static void injectTrustStore(EaafSslContextBuilder sslContextBuilder,
- Pair<KeyStore, Provider> trustStore, boolean trustAllServerCertificates, String friendlyName)
+ KeyStore trustStore, boolean trustAllServerCertificates, String friendlyName)
throws NoSuchAlgorithmException, KeyStoreException {
TrustStrategy trustStrategy = null;
@@ -348,14 +385,12 @@ public class HttpUtils {
}
- KeyStore trustStoreImpl = null;
if (trustStore != null) {
log.info("Http-client: {} uses custom TrustStore.", friendlyName);
- trustStoreImpl = trustStore.getFirst();
}
- sslContextBuilder.loadTrustMaterial(trustStoreImpl, trustStrategy);
+ sslContextBuilder.loadTrustMaterial(trustStore, trustStrategy);
}
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/ConfigurationUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/ConfigurationUtils.java
index 2e6d53c9..ae39ba49 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/ConfigurationUtils.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/ConfigurationUtils.java
@@ -1,5 +1,7 @@
package at.gv.egiz.eaaf.core.impl.utils;
+import org.apache.commons.lang3.StringUtils;
+
import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
import lombok.extern.slf4j.Slf4j;
@@ -15,6 +17,26 @@ public class ConfigurationUtils {
/**
+ * Read String value from configuration.
+ *
+ * @param basicConfig Configuration object
+ * @param propertyKey Configuration key
+ * @return Configuration value
+ * @throws EaafConfigurationException If configuration value does not exist
+ */
+ public static String parseString(IConfiguration basicConfig, String propertyKey)
+ throws EaafConfigurationException {
+ String value = basicConfig.getBasicConfiguration(propertyKey);
+ if (StringUtils.isEmpty(value)) {
+ log.error("Can not find String value from configuration: {}", propertyKey);
+ throw new EaafConfigurationException("internal.configuration.00", new Object[] { propertyKey });
+
+ }
+
+ return value;
+ }
+
+ /**
* Parse Integer value from configuration.
*
* @param basicConfig Configuration object
@@ -36,6 +58,28 @@ public class ConfigurationUtils {
}
/**
+ * Parse Integer value from configuration.
+ *
+ * @param basicConfig Configuration object
+ * @param propertyKey Configuration key
+ * @param defaultValue Default value of configuration-key does not exist
+ * @return Configuration value
+ * @throws EaafConfigurationException If configuration value is not an Integer
+ */
+ public static int parseInteger(IConfiguration basicConfig, String propertyKey, int defaultValue)
+ throws EaafConfigurationException {
+ try {
+ return Integer.parseInt(basicConfig.getBasicConfiguration(propertyKey, String.valueOf(defaultValue)));
+
+ } catch (NumberFormatException e) {
+ log.error("Can not read Integer value from configuration: {}", propertyKey, e);
+ throw new EaafConfigurationException("internal.configuration.02",
+ new Object[] { propertyKey, Integer.class.getSimpleName() }, e);
+
+ }
+ }
+
+ /**
* Parse Long value from configuration.
*
* @param basicConfig Configuration object
@@ -59,4 +103,5 @@ public class ConfigurationUtils {
private ConfigurationUtils() {
}
+
}
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Random.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Random.java
index 6c0a288f..3d1e8ba3 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Random.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Random.java
@@ -57,7 +57,7 @@ public class Random {
random = SecureRandom.getInstance("SHA256PRNG-FIPS186");
} catch (final NoSuchAlgorithmException e) {
- log.warn(
+ log.info(
"Can NOT initialize SecureRandom with: 'SHA256PRNG-FIPS186'. Use 'StrongSecureRandom' as backup");
random = SecureRandomHolder.getInstance();
diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java
index 269c516e..33ba96e2 100644
--- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java
+++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java
@@ -1,6 +1,7 @@
package at.gv.egiz.eaaf.core.test.http;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -19,6 +20,7 @@ import java.security.cert.X509Certificate;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.HttpHostConnectException;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
@@ -41,6 +43,7 @@ import org.springframework.test.annotation.DirtiesContext.MethodMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;
import at.gv.egiz.eaaf.core.exceptions.EaafException;
import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory;
import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration;
@@ -51,8 +54,10 @@ import at.gv.egiz.eaaf.core.impl.http.HttpClientConfiguration;
import at.gv.egiz.eaaf.core.impl.http.HttpUtils;
import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory;
import at.gv.egiz.eaaf.core.impl.utils.StreamUtils;
+import at.gv.egiz.eaaf.core.test.dummy.DummyAuthConfigMap;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
+import lombok.SneakyThrows;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@@ -66,8 +71,14 @@ import okhttp3.tls.HeldCertificate;
@DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
public class HttpClientFactoryTest {
- @Autowired private EaafKeyStoreFactory keyStoreFactory;
- @Autowired private IHttpClientFactory httpClientFactory;
+ @Autowired
+ EaafKeyStoreFactory keyStoreFactory;
+
+ @Autowired
+ IHttpClientFactory httpClientFactory;
+
+ @Autowired
+ DummyAuthConfigMap basicConfig;
private MockWebServer mockWebServer = null;
private HttpUrl mockServerUrl;
@@ -99,6 +110,8 @@ public class HttpClientFactoryTest {
*/
@Before
public void setup() {
+ basicConfig.putConfigValue("client.http.connection.proxy.host", "localhost");
+ basicConfig.putConfigValue("client.http.connection.proxy.port", "8080");
}
@@ -169,6 +182,64 @@ public class HttpClientFactoryTest {
}
@Test
+ @SneakyThrows
+ public void clientWithHttpProxyModeWrongPort() throws EaafException {
+ basicConfig.putConfigValue("client.http.connection.proxy.port", "8081");
+
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit-withProxy");
+ config.setEnableHttpProxyMode(true);
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("httpClient", client);
+
+ mockWebServer = new MockWebServer();
+ mockServerUrl = mockWebServer.url("/sp/junit");
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("GetData"));
+
+ final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString());
+ assertThrows(HttpHostConnectException.class, () -> client.execute(httpGet1));
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void clientWithHttpProxyMode() throws EaafException {
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit-withProxy");
+ config.setEnableHttpProxyMode(true);
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("httpClient", client);
+
+ MockWebServer mockProxy = new MockWebServer();
+ mockProxy.start(8080);
+ mockProxy.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("GetData"));
+
+ mockWebServer = new MockWebServer();
+ mockServerUrl = mockWebServer.url("/sp/junit");
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("GetData"));
+
+ final HttpUriRequest httpGet1 = new HttpGet(mockServerUrl.url().toString());
+ final CloseableHttpResponse httpResp1 = client.execute(httpGet1);
+ Assert.assertEquals("http statusCode", 200, httpResp1.getCode());
+
+ mockProxy.close();
+
+ }
+
+ @Test
+ public void clientWithHttpProxyModeMissingHost() throws EaafException {
+ basicConfig.removeConfigValue("client.http.connection.proxy.host");
+
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit-withProxy");
+ config.setEnableHttpProxyMode(true);
+ assertThrows(EaafConfigurationException.class, () -> httpClientFactory.getHttpClient(config));
+
+ }
+
+ @Test
public void getCustomClientUnknownAuthMethod() throws EaafException {
final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
config.setAuthMode(RandomStringUtils.randomAlphabetic(5));
@@ -214,6 +285,79 @@ public class HttpClientFactoryTest {
}
@Test
+ public void getCustomClientBasicAuthWithSslTrustAll() throws EaafException, ClientProtocolException,
+ IOException, KeyStoreException {
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
+ config.setEnablePreEmptiveHttpBasicAuth(false);
+ config.setAuthMode("password");
+ config.setUsername("jUnit");
+ config.setPassword("password");
+ config.setDisableTlsHostCertificateValidation(true);
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("httpClient", client);
+
+ // set-up mock-up web-server with SSL client authentication
+ final String localhost = InetAddress.getByName("localhost").getCanonicalHostName();
+ final HeldCertificate localhostCertificate = new HeldCertificate.Builder()
+ .addSubjectAlternativeName(localhost)
+ .build();
+ final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+ .heldCertificate(localhostCertificate)
+ .build();
+ mockWebServer = new MockWebServer();
+ mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false);
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("Successful auth!"));
+ mockServerUrl = mockWebServer.url("/sp/junit");
+
+ // perform test request
+ final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString());
+ final CloseableHttpResponse httpResp2 = client.execute(httpGet2);
+ Assert.assertEquals("http statusCode", 200, httpResp2.getCode());
+
+ }
+
+ @Test
+ public void getCustomClientBasicAuthWithSsl() throws EaafException, ClientProtocolException,
+ IOException, KeyStoreException {
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
+ config.setEnablePreEmptiveHttpBasicAuth(false);
+ config.setAuthMode("password");
+ config.setUsername("jUnit");
+ config.setPassword("password");
+
+ final String current = new java.io.File(".").getCanonicalPath();
+ System.setProperty("javax.net.ssl.trustStoreType", "jks");
+ System.setProperty("javax.net.ssl.trustStore",
+ current + "/src/test/resources/data/ssL_truststore.jks");
+ System.setProperty("javax.net.ssl.trustStorePassword",
+ "password");
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("httpClient", client);
+
+ // set-up mock-up web-server with SSL client authentication
+ final String localhost = InetAddress.getByName("localhost").getCanonicalHostName();
+ final HeldCertificate localhostCertificate = new HeldCertificate.Builder()
+ .addSubjectAlternativeName(localhost)
+ .build();
+ final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+ .heldCertificate(localhostCertificate)
+ .build();
+ mockWebServer = new MockWebServer();
+ mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false);
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("Successful auth!"));
+ mockServerUrl = mockWebServer.url("/sp/junit");
+
+ // perform test request
+ final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString());
+ assertThrows(Exception.class, () -> client.execute(httpGet2));
+
+ }
+
+ @Test
public void getCustomClientBasicAuthWithPreEmptive() throws EaafException, ClientProtocolException,
IOException, InterruptedException {
final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
@@ -257,34 +401,92 @@ public class HttpClientFactoryTest {
}
@Test
- public void httpPostRetryNotAllowed() throws EaafException, InterruptedException,
- ClientProtocolException, IOException {
- final HttpClientConfiguration config =
- new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3));
- config.setHttpErrorRetryCount(2);
+ public void withCustomTrustStore() throws EaafException, ClientProtocolException,
+ IOException, KeyStoreException {
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
+ config.setEnablePreEmptiveHttpBasicAuth(false);
+ config.setAuthMode("password");
+ config.setUsername("jUnit");
+ config.setPassword("password");
- final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
- Assert.assertNotNull("No httpClient", client);
+ final String current = new java.io.File(".").getCanonicalPath();
+ config.buildTrustStoreConfig("jks", "file:" + current + "/src/test/resources/data/ssL_truststore.jks",
+ "password", null);
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("httpClient", client);
+ // set-up mock-up web-server with SSL client authentication
+ final String localhost = InetAddress.getByName("localhost").getCanonicalHostName();
+ final HeldCertificate localhostCertificate = new HeldCertificate.Builder()
+ .addSubjectAlternativeName(localhost)
+ .build();
+ final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+ .heldCertificate(localhostCertificate)
+ .build();
mockWebServer = new MockWebServer();
+ mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false);
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("Successful auth!"));
mockServerUrl = mockWebServer.url("/sp/junit");
- mockWebServer.enqueue(new MockResponse()
- .setSocketPolicy(SocketPolicy.NO_RESPONSE)
- .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT));
+
+ // perform test request
+ final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString());
+ assertThrows(Exception.class, () -> client.execute(httpGet2));
+
+ }
+
+ @Test
+ public void withWrongCustomTrustStore() throws EaafException, ClientProtocolException,
+ IOException, KeyStoreException {
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
+ config.setEnablePreEmptiveHttpBasicAuth(false);
+ config.setAuthMode("password");
+ config.setUsername("jUnit");
+ config.setPassword("password");
+
+ final String current = new java.io.File(".").getCanonicalPath();
+ config.buildTrustStoreConfig("jks", "file:" + current + "/src/test/resources/data/ssL_truststore.jks",
+ "password", null);
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("httpClient", client);
+
+ // set-up mock-up web-server with SSL client authentication
+ final String localhost = InetAddress.getByName("localhost").getCanonicalHostName();
+ final HeldCertificate localhostCertificate = new HeldCertificate.Builder()
+ .addSubjectAlternativeName(localhost)
+ .build();
+ final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+ .heldCertificate(localhostCertificate)
+ .build();
+ mockWebServer = new MockWebServer();
+ mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false);
mockWebServer.enqueue(new MockResponse().setResponseCode(200)
- .setBody("GetData"));
+ .setBody("Successful auth!"));
+ mockServerUrl = mockWebServer.url("/sp/junit");
- //request webservice
- final HttpUriRequest httpGet1 = new HttpPost(mockServerUrl.url().toString());
- try {
- client.execute(httpGet1);
- Assert.fail("HTTP POST retry not allowed");
+ // perform test request
+ final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString());
+ assertThrows(Exception.class, () -> client.execute(httpGet2));
- } catch (final SocketTimeoutException e) {
- Assert.assertNotNull("No errorMsg", e.getMessage());
+ }
- }
+ @Test
+ public void withWrongConfigCustomTrustStore() throws EaafException, ClientProtocolException,
+ IOException, KeyStoreException {
+ final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
+ config.setEnablePreEmptiveHttpBasicAuth(false);
+ config.setAuthMode("password");
+ config.setUsername("jUnit");
+ config.setPassword("password");
+
+ final String current = new java.io.File(".").getCanonicalPath();
+ config.buildTrustStoreConfig("jks", "file:" + current + "/src/test/resources/data/ssL_truststore.jks",
+ "wrongPassword", null);
+
+ EaafException error = assertThrows(EaafException.class, () -> httpClientFactory.getHttpClient(config));
+ Assert.assertEquals("wrong errorCode", "internal.keystore.06", error.getErrorId());
}
@@ -415,6 +617,68 @@ public class HttpClientFactoryTest {
}
@Test
+ public void testHttpClientRetryOneTimePost() throws EaafException, InterruptedException,
+ ClientProtocolException, IOException {
+ final HttpClientConfiguration config =
+ new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3));
+ config.setHttpErrorRetryCount(2);
+ config.setHttpPostRetryAllowed(true);
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("No httpClient", client);
+
+ mockWebServer = new MockWebServer();
+ mockServerUrl = mockWebServer.url("/sp/junit");
+ mockWebServer.enqueue(new MockResponse()
+ .setSocketPolicy(SocketPolicy.NO_RESPONSE)
+ .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT));
+
+ String bodyData = RandomStringUtils.randomAlphanumeric(10);
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody(bodyData));
+
+ // request webservice
+ final HttpUriRequest httpGet1 = new HttpPost(mockServerUrl.url().toString());
+ final Triple<StatusLine, ByteArrayInputStream, ContentType> httpResp1 = client.execute(httpGet1,
+ HttpUtils.bodyStatusCodeResponseHandler());
+ Assert.assertEquals("http statusCode", 200, httpResp1.getFirst().getStatusCode());
+ Assert.assertEquals("http statusCode", bodyData, new String(StreamUtils.readStream(httpResp1
+ .getSecond())));
+
+ }
+
+ @Test
+ public void httpPostRetryNotAllowed() throws EaafException, InterruptedException,
+ ClientProtocolException, IOException {
+ final HttpClientConfiguration config =
+ new HttpClientConfiguration("jUnit_retry_" + RandomStringUtils.randomAlphabetic(3));
+ config.setHttpErrorRetryCount(2);
+ config.setHttpPostRetryAllowed(false);
+
+ final CloseableHttpClient client = httpClientFactory.getHttpClient(config);
+ Assert.assertNotNull("No httpClient", client);
+
+ mockWebServer = new MockWebServer();
+ mockServerUrl = mockWebServer.url("/sp/junit");
+ mockWebServer.enqueue(new MockResponse()
+ .setSocketPolicy(SocketPolicy.NO_RESPONSE)
+ .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT));
+ mockWebServer.enqueue(new MockResponse().setResponseCode(200)
+ .setBody("GetData"));
+
+ // request webservices
+ final HttpUriRequest httpGet1 = new HttpPost(mockServerUrl.url().toString());
+ try {
+ client.execute(httpGet1);
+ Assert.fail("HTTP POST retry not allowed");
+
+ } catch (final SocketTimeoutException e) {
+ Assert.assertNotNull("No errorMsg", e.getMessage());
+
+ }
+ }
+
+ @Test
public void getCustomClientBasicAuthNoPassword() throws EaafException {
final HttpClientConfiguration config = new HttpClientConfiguration("jUnit");
config.setAuthMode("password");
diff --git a/eaaf_core_utils/src/test/resources/data/config1.properties b/eaaf_core_utils/src/test/resources/data/config1.properties
index 12209d21..ec5e45a7 100644
--- a/eaaf_core_utils/src/test/resources/data/config1.properties
+++ b/eaaf_core_utils/src/test/resources/data/config1.properties
@@ -4,10 +4,14 @@ security.hsmfacade.trustedsslcert=src/test/resources/data/hsm_facade_trust_root.
security.hsmfacade.username=authhandler-junit
security.hsmfacade.password=supersecret123
+client.http.connection.pool.use=true
client.http.connection.timeout.socket=2
client.http.connection.timeout.connection=2
client.http.connection.timeout.request=2
+client.http.connection.proxy.host=localhost
+client.http.connection.proxy.port=8080
+
core.pendingrequestid.maxlifetime=180
core.pendingrequestid.digist.type=passphrase
core.pendingrequestid.digist.secret=pendingReqIdSecret
diff --git a/eaaf_modules/eaaf_module_auth_sl20/pom.xml b/eaaf_modules/eaaf_module_auth_sl20/pom.xml
index 22b3d0e8..fafe7b92 100644
--- a/eaaf_modules/eaaf_module_auth_sl20/pom.xml
+++ b/eaaf_modules/eaaf_module_auth_sl20/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_modules</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<artifactId>eaaf_module_auth_sl20</artifactId>
<name>Generic SL2.0 authentication</name>
diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java
index a2b2a132..b201241b 100644
--- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java
+++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java
@@ -136,8 +136,10 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask
SL20ResponseUtils.buildResponse(request, response, pendingReq, resumeEndpoint,
SL20JsonExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false),
authConfig);
+ log.info("Return to A-Trust and forwarding to AuthHandler ... ");
} else {
+ log.warn("Return with SL20-General-transport-Binding-error, because no request object was found");
SL20ResponseUtils.buildErrorResponse(response, "2000", "General transport Binding error");
}
diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/test/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/AbstractJsonSecurityUtilsTest.java b/eaaf_modules/eaaf_module_auth_sl20/src/test/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/AbstractJsonSecurityUtilsTest.java
index 18ac843f..c9e6ea4e 100644
--- a/eaaf_modules/eaaf_module_auth_sl20/src/test/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/AbstractJsonSecurityUtilsTest.java
+++ b/eaaf_modules/eaaf_module_auth_sl20/src/test/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/AbstractJsonSecurityUtilsTest.java
@@ -55,6 +55,9 @@ public abstract class AbstractJsonSecurityUtilsTest {
*/
@BeforeClass
public static void classInitializer() {
+
+ System.setProperty("javax.net.ssl.trustStoreType", "jks");
+
IAIK.addAsProvider();
ECCelerate.addAsProvider();
@@ -68,7 +71,8 @@ public abstract class AbstractJsonSecurityUtilsTest {
public static final void classFinisher() {
Security.removeProvider(IAIK.getInstance().getName());
Security.removeProvider(ECCelerate.getInstance().getName());
-
+ System.clearProperty("javax.net.ssl.trustStoreType");
+
}
protected abstract void setRsaSigningKey();
diff --git a/eaaf_modules/eaaf_module_moa-sig/pom.xml b/eaaf_modules/eaaf_module_moa-sig/pom.xml
index 2d8d16cf..5d415d64 100644
--- a/eaaf_modules/eaaf_module_moa-sig/pom.xml
+++ b/eaaf_modules/eaaf_module_moa-sig/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_modules</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<artifactId>eaaf_module_moa-sig</artifactId>
<name>MOA-Sig signature verification module</name>
diff --git a/eaaf_modules/eaaf_module_pvp2_core/pom.xml b/eaaf_modules/eaaf_module_pvp2_core/pom.xml
index 1176e1b8..40796cf0 100644
--- a/eaaf_modules/eaaf_module_pvp2_core/pom.xml
+++ b/eaaf_modules/eaaf_module_pvp2_core/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_modules</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<artifactId>eaaf_module_pvp2_core</artifactId>
<name>eaaf_module_pvp2_core</name>
diff --git a/eaaf_modules/eaaf_module_pvp2_idp/pom.xml b/eaaf_modules/eaaf_module_pvp2_idp/pom.xml
index 48c1e8e0..93fab522 100644
--- a/eaaf_modules/eaaf_module_pvp2_idp/pom.xml
+++ b/eaaf_modules/eaaf_module_pvp2_idp/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_modules</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<artifactId>eaaf_module_pvp2_idp</artifactId>
<name>eaaf_module_pvp2_idp</name>
diff --git a/eaaf_modules/eaaf_module_pvp2_sp/pom.xml b/eaaf_modules/eaaf_module_pvp2_sp/pom.xml
index 3ea61b1c..03dcdfc1 100644
--- a/eaaf_modules/eaaf_module_pvp2_sp/pom.xml
+++ b/eaaf_modules/eaaf_module_pvp2_sp/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>at.gv.egiz.eaaf</groupId>
<artifactId>eaaf_modules</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<artifactId>eaaf_module_pvp2_sp</artifactId>
<name>eaaf_module_pvp2_sp</name>
diff --git a/eaaf_modules/pom.xml b/eaaf_modules/pom.xml
index 050983b7..b1be9970 100644
--- a/eaaf_modules/pom.xml
+++ b/eaaf_modules/pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
</parent>
<groupId>at.gv.egiz.eaaf</groupId>
diff --git a/pom.xml b/pom.xml
index ebecf0de..468fb8d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>at.gv.egiz</groupId>
<artifactId>eaaf</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.2</version>
<packaging>pom</packaging>
<name>EGIZ EAAF components</name>
@@ -25,6 +25,7 @@
<MOA.spss.server.moa-sig-lib.version>3.2.2</MOA.spss.server.moa-sig-lib.version>
<MOA.spss.tsl_lib.version>2.1.1</MOA.spss.tsl_lib.version>
+ <postgresql.version>42.6.2</postgresql.version>
<!-- IAIK libs -->
<iaik.prod.iaik_cms.version>6.0_moa</iaik.prod.iaik_cms.version>
@@ -46,49 +47,49 @@
<io.grpc-core.version>1.53.0</io.grpc-core.version>
<!-- Other third-party libs -->
- <spring-boot-starter-web.version>3.1.7</spring-boot-starter-web.version>
- <org.springframework.version>6.0.16</org.springframework.version>
- <org.apache.tomcat.embed.version>10.1.18</org.apache.tomcat.embed.version>
+ <spring-boot-starter-web.version>3.1.10</spring-boot-starter-web.version>
+ <org.springframework.version>6.0.18</org.springframework.version>
+ <org.apache.tomcat.embed.version>10.1.20</org.apache.tomcat.embed.version>
<org.opensaml.version>5.0.0</org.opensaml.version>
- <org.apache.santuario.xmlsec.version>3.0.3</org.apache.santuario.xmlsec.version>
+ <org.apache.santuario.xmlsec.version>3.0.4</org.apache.santuario.xmlsec.version>
<org.cryptacular.version>1.2.6</org.cryptacular.version>
<org.bouncycastle.bcprov-jdk18on.version>1.76</org.bouncycastle.bcprov-jdk18on.version>
<!-- currently, SSL Client authentication with re-recognition doesn't work with bctls-jdk18o > 1.71.1 -->
<org.bouncycastle.bctls-jdk18on.version>1.71.1</org.bouncycastle.bctls-jdk18on.version>
- <org.slf4j.version>2.0.11</org.slf4j.version>
- <log4j.version>2.22.1</log4j.version>
+ <org.slf4j.version>2.0.12</org.slf4j.version>
+ <log4j.version>2.23.1</log4j.version>
<ch.qos.logback.version>1.4.14</ch.qos.logback.version>
<org.owasp.encoder.version>1.2.3</org.owasp.encoder.version>
<org.apache.commons-lang3.version>3.14.0</org.apache.commons-lang3.version>
<org.apache.commons-text.version>1.11.0</org.apache.commons-text.version>
<org.apache.commons-collections4>4.4</org.apache.commons-collections4>
- <commons-io.version>2.15.1</commons-io.version>
- <commons-codec.version>1.16.0</commons-codec.version>
+ <commons-io.version>2.16.0</commons-io.version>
+ <commons-codec.version>1.16.1</commons-codec.version>
<commons-fileupload2.version>2.0.0-M1</commons-fileupload2.version>
<jakarta.servlet-api>6.0.0</jakarta.servlet-api>
<jakarta.annotation-api.version>2.1.1</jakarta.annotation-api.version>
- <joda-time.version>2.12.6</joda-time.version>
+ <joda-time.version>2.12.7</joda-time.version>
<org.apache.velocity.version>2.3</org.apache.velocity.version>
- <com.google.guava.version>33.0.0-jre</com.google.guava.version>
- <org.bitbucket.b_c.jose4j.version>0.9.4</org.bitbucket.b_c.jose4j.version>
+ <com.google.guava.version>33.1.0-jre</com.google.guava.version>
+ <org.bitbucket.b_c.jose4j.version>0.9.6</org.bitbucket.b_c.jose4j.version>
<jsr305.version>3.0.2</jsr305.version>
<httpclient.version>5.2.3</httpclient.version>
- <com.fasterxml.jackson.core.version>2.16.1</com.fasterxml.jackson.core.version>
- <com.fasterxml.jackson.databind.version>2.16.1</com.fasterxml.jackson.databind.version>
+ <com.fasterxml.jackson.core.version>2.17.0</com.fasterxml.jackson.core.version>
+ <com.fasterxml.jackson.databind.version>2.17.0</com.fasterxml.jackson.databind.version>
<gson.version>2.10.1</gson.version>
<jaxen.jaxen.version>2.0.0</jaxen.jaxen.version>
<xerces.version>2.12.2</xerces.version>
<xalan.version>2.7.1</xalan.version>
- <woodstox-core.version>6.5.1</woodstox-core.version>
+ <woodstox-core.version>6.6.2</woodstox-core.version>
<snakeyaml.version>2.2</snakeyaml.version>
<!-- jUnit testing -->
@@ -354,6 +355,11 @@
<artifactId>tsl-lib</artifactId>
<version>${MOA.spss.tsl_lib.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>${postgresql.version}</version>
+ </dependency>
<!-- IAIK libs -->
@@ -694,6 +700,11 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
+ <version>${com.fasterxml.jackson.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-joda</artifactId>
<version>${com.fasterxml.jackson.core.version}</version>
</dependency>