diff options
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> @@ -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> |