From ed679a72cd5ee6a9a6b9d049aaccf1e8751dfb2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 02:33:05 +0000 Subject: [PATCH 01/16] Bump org.openapitools:openapi-generator-maven-plugin from 7.2.0 to 7.3.0 Bumps org.openapitools:openapi-generator-maven-plugin from 7.2.0 to 7.3.0. --- updated-dependencies: - dependency-name: org.openapitools:openapi-generator-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 606baa41..06fbfe39 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ - 7.2.0 + 7.3.0 5.10.2 4.0.0 From 38decb2bc91ad7742ca72ccf49ff6c14a35be282 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 02:57:19 +0000 Subject: [PATCH 02/16] Bump net.javacrumbs.shedlock:shedlock-bom from 5.10.2 to 5.11.0 Bumps [net.javacrumbs.shedlock:shedlock-bom](https://github.com/lukas-krecan/ShedLock) from 5.10.2 to 5.11.0. - [Commits](https://github.com/lukas-krecan/ShedLock/compare/shedlock-parent-5.10.2...shedlock-parent-5.11.0) --- updated-dependencies: - dependency-name: net.javacrumbs.shedlock:shedlock-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 06fbfe39..d3e00637 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ 7.3.0 - 5.10.2 + 5.11.0 4.0.0 2.2.20 2.3.0 From 7af9241e3011508fdee5a592fdba37e423c347e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Tue, 20 Feb 2024 11:31:11 +0100 Subject: [PATCH 03/16] Fix #913: Improve error handling in MobileTokenController (#992) --- .../controller/api/MobileTokenController.java | 153 +++++++++++++++--- .../DefaultExceptionHandler.java | 13 ++ .../RemoteCommunicationException.java | 40 +++++ .../mtoken/model/enumeration/ErrorCode.java | 12 ++ 4 files changed, 200 insertions(+), 18 deletions(-) create mode 100644 enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/RemoteCommunicationException.java diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/MobileTokenController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/MobileTokenController.java index 1ef71aeb..61927ad4 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/MobileTokenController.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/MobileTokenController.java @@ -21,12 +21,15 @@ import com.wultra.app.enrollmentserver.errorhandling.MobileTokenAuthException; import com.wultra.app.enrollmentserver.errorhandling.MobileTokenConfigurationException; import com.wultra.app.enrollmentserver.errorhandling.MobileTokenException; +import com.wultra.app.enrollmentserver.errorhandling.RemoteCommunicationException; import com.wultra.app.enrollmentserver.impl.service.MobileTokenService; import com.wultra.app.enrollmentserver.impl.service.OperationApproveParameterObject; import com.wultra.core.http.common.request.RequestContext; import com.wultra.core.http.common.request.RequestContextConverter; import com.wultra.security.powerauth.client.model.error.PowerAuthClientException; +import com.wultra.security.powerauth.client.model.error.PowerAuthError; import com.wultra.security.powerauth.lib.mtoken.model.entity.Operation; +import com.wultra.security.powerauth.lib.mtoken.model.enumeration.ErrorCode; import com.wultra.security.powerauth.lib.mtoken.model.request.OperationApproveRequest; import com.wultra.security.powerauth.lib.mtoken.model.request.OperationDetailRequest; import com.wultra.security.powerauth.lib.mtoken.model.request.OperationRejectRequest; @@ -68,6 +71,13 @@ @RequestMapping("api/auth/token/app") public class MobileTokenController { + private static final String APPLICATION_NOT_FOUND = "ERR0015"; + private static final String INVALID_REQUEST = "ERR0024"; + private static final String OPERATION_NOT_FOUND = "ERR0034"; + private static final String OPERATION_INVALID_STATE = "ERR0036"; + private static final String OPERATION_APPROVE_FAILURE = "ERR0037"; + private static final String OPERATION_REJECT_FAILURE = "ERR0038"; + private static final Logger logger = LoggerFactory.getLogger(MobileTokenController.class); // Disallowed flags contain onboarding flags used before onboarding process is finished @@ -101,7 +111,7 @@ public MobileTokenController(MobileTokenService mobileTokenService) { PowerAuthSignatureTypes.POSSESSION_KNOWLEDGE, PowerAuthSignatureTypes.POSSESSION_KNOWLEDGE_BIOMETRY }) - public ObjectResponse operationList(@Parameter(hidden = true) PowerAuthApiAuthentication auth, @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException { + public ObjectResponse operationList(@Parameter(hidden = true) PowerAuthApiAuthentication auth, @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException, RemoteCommunicationException { try { if (auth != null) { final String userId = auth.getUserId(); @@ -115,8 +125,24 @@ public ObjectResponse operationList(@Parameter(hidden = t throw new MobileTokenAuthException(); } } catch (PowerAuthClientException e) { - logger.error("Unable to call upstream service.", e); - throw new MobileTokenAuthException(); + final String errorCode = e.getPowerAuthError().map(PowerAuthError::getCode).orElse("ERROR_CODE_MISSING"); + switch (errorCode) { + case APPLICATION_NOT_FOUND -> { + logger.info("Application ID: {} not found: {}", auth.getApplicationId(), e.getMessage()); + logger.debug("Application ID: {} not found.", auth.getApplicationId(), e); + throw new MobileTokenException(ErrorCode.INVALID_APPLICATION, "No application was found with the provided identifier."); + } + case INVALID_REQUEST -> { + logger.info("Request validation error: {}", e.getMessage()); + logger.debug("Request validation error.", e); + throw new MobileTokenException(ErrorCode.INVALID_REQUEST, "Request validation error: %s".formatted(e.getMessage())); + } + default -> { + logger.warn("Calling PowerAuth service failed: {}", e.getMessage()); + logger.debug("Calling PowerAuth service failed.", e); + throw new RemoteCommunicationException("Unable to call upstream service."); + } + } } } @@ -138,7 +164,7 @@ public ObjectResponse operationList(@Parameter(hidden = t }) public ObjectResponse getOperationDetail(@RequestBody ObjectRequest request, @Parameter(hidden = true) PowerAuthApiAuthentication auth, - @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException { + @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException, RemoteCommunicationException { try { if (auth != null) { final String operationId = request.getRequestObject().getId(); @@ -151,8 +177,24 @@ public ObjectResponse getOperationDetail(@RequestBody ObjectRequest { + logger.info("Operation ID: {} not found: {}", request.getRequestObject().getId(), e.getMessage()); + logger.debug("Operation ID: {} not found.", request.getRequestObject().getId(), e); + throw new MobileTokenException(ErrorCode.INVALID_OPERATION, "No operation was found with the provided identifier."); + } + case INVALID_REQUEST -> { + logger.info("Request validation error: {}", e.getMessage()); + logger.debug("Request validation error.", e); + throw new MobileTokenException(ErrorCode.INVALID_REQUEST, "Request validation error: %s".formatted(e.getMessage())); + } + default -> { + logger.warn("Calling PowerAuth service failed: {}", e.getMessage()); + logger.debug("Calling PowerAuth service failed.", e); + throw new RemoteCommunicationException("Unable to call upstream service."); + } + } } } @@ -174,7 +216,7 @@ public ObjectResponse getOperationDetail(@RequestBody ObjectRequest claimOperation(@RequestBody ObjectRequest request, @Parameter(hidden = true) PowerAuthApiAuthentication auth, - @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException { + @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException, RemoteCommunicationException { try { if (auth != null) { final String operationId = request.getRequestObject().getId(); @@ -187,8 +229,24 @@ public ObjectResponse claimOperation(@RequestBody ObjectRequest { + logger.info("Operation ID: {} not found: {}", request.getRequestObject().getId(), e.getMessage()); + logger.debug("Operation ID: {} not found.", request.getRequestObject().getId(), e); + throw new MobileTokenException(ErrorCode.INVALID_OPERATION, "No operation was found with the provided identifier."); + } + case INVALID_REQUEST -> { + logger.info("Request validation error: {}", e.getMessage()); + logger.debug("Request validation error.", e); + throw new MobileTokenException(ErrorCode.INVALID_REQUEST, "Request validation error: %s".formatted(e.getMessage())); + } + default -> { + logger.warn("Calling PowerAuth service failed: {}", e.getMessage()); + logger.debug("Calling PowerAuth service failed.", e); + throw new RemoteCommunicationException("Unable to call upstream service."); + } + } } } @@ -206,7 +264,7 @@ public ObjectResponse claimOperation(@RequestBody ObjectRequest operationListAll(@Parameter(hidden = true) PowerAuthApiAuthentication auth, @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException { + public ObjectResponse operationListAll(@Parameter(hidden = true) PowerAuthApiAuthentication auth, @Parameter(hidden = true) Locale locale) throws MobileTokenException, MobileTokenConfigurationException, RemoteCommunicationException { try { if (auth != null) { final String userId = auth.getUserId(); @@ -219,8 +277,24 @@ public ObjectResponse operationListAll(@Parameter(hidden throw new MobileTokenAuthException(); } } catch (PowerAuthClientException e) { - logger.error("Unable to call upstream service.", e); - throw new MobileTokenAuthException(); + final String errorCode = e.getPowerAuthError().map(PowerAuthError::getCode).orElse("ERROR_CODE_MISSING"); + switch (errorCode) { + case APPLICATION_NOT_FOUND -> { + logger.info("Application ID: {} not found: {}", auth.getApplicationId(), e.getMessage()); + logger.debug("Application ID: {} not found.", auth.getApplicationId(), e); + throw new MobileTokenException(ErrorCode.INVALID_APPLICATION, "No application was found with the provided identifier."); + } + case INVALID_REQUEST -> { + logger.info("Request validation error: {}", e.getMessage()); + logger.debug("Request validation error.", e); + throw new MobileTokenException(ErrorCode.INVALID_REQUEST, "Request validation error: %s".formatted(e.getMessage())); + } + default -> { + logger.warn("Calling PowerAuth service failed: {}", e.getMessage()); + logger.debug("Calling PowerAuth service failed.", e); + throw new RemoteCommunicationException("Unable to call upstream service."); + } + } } } @@ -242,7 +316,7 @@ public ObjectResponse operationListAll(@Parameter(hidden public Response operationApprove( @RequestBody ObjectRequest request, @Parameter(hidden = true) PowerAuthApiAuthentication auth, - HttpServletRequest servletRequest) throws MobileTokenException { + HttpServletRequest servletRequest) throws MobileTokenException, RemoteCommunicationException { try { final OperationApproveRequest requestObject = request.getRequestObject(); @@ -290,8 +364,30 @@ public Response operationApprove( throw new MobileTokenAuthException(); } } catch (PowerAuthClientException e) { - logger.error("Unable to call upstream service.", e); - throw new MobileTokenAuthException(); + final String errorCode = e.getPowerAuthError().map(PowerAuthError::getCode).orElse("ERROR_CODE_MISSING"); + switch (errorCode) { + case APPLICATION_NOT_FOUND -> { + final String applicationId = auth != null ? auth.getApplicationId() : null; + logger.info("Application ID: {} not found: {}", applicationId, e.getMessage()); + logger.debug("Application ID: {} not found.", applicationId, e); + throw new MobileTokenException(ErrorCode.INVALID_APPLICATION, "No application was found with the provided identifier."); + } + case OPERATION_NOT_FOUND, OPERATION_APPROVE_FAILURE, OPERATION_INVALID_STATE -> { + logger.info("Operation ID: {} not found or is in unexpected state: {}", request.getRequestObject().getId(), e.getMessage()); + logger.debug("Operation ID: {} not found or is in unexpected state.", request.getRequestObject().getId(), e); + throw new MobileTokenException(ErrorCode.INVALID_OPERATION, "Operation not found or is in an unexpected state."); + } + case INVALID_REQUEST -> { + logger.info("Request validation error: {}", e.getMessage()); + logger.debug("Request validation error.", e); + throw new MobileTokenException(ErrorCode.INVALID_REQUEST, "Request validation error: %s".formatted(e.getMessage())); + } + default -> { + logger.warn("Calling PowerAuth service failed: {}", e.getMessage()); + logger.debug("Calling PowerAuth service failed.", e); + throw new RemoteCommunicationException("Unable to call upstream service."); + } + } } } @@ -320,7 +416,7 @@ private static String fetchProximityCheckOtp(OperationApproveRequest requestObje public Response operationReject( @RequestBody ObjectRequest request, @Parameter(hidden = true) PowerAuthApiAuthentication auth, - HttpServletRequest servletRequest) throws MobileTokenException { + HttpServletRequest servletRequest) throws MobileTokenException, RemoteCommunicationException { try { final OperationRejectRequest requestObject = request.getRequestObject(); @@ -342,8 +438,29 @@ public Response operationReject( throw new MobileTokenAuthException(); } } catch (PowerAuthClientException e) { - logger.error("Unable to call upstream service.", e); - throw new MobileTokenAuthException(); + final String errorCode = e.getPowerAuthError().map(PowerAuthError::getCode).orElse("ERROR_CODE_MISSING"); + switch (errorCode) { + case APPLICATION_NOT_FOUND -> { + logger.info("Application ID: {} not found: {}", auth.getApplicationId(), e.getMessage()); + logger.debug("Application ID: {} not found.", auth.getApplicationId(), e); + throw new MobileTokenException(ErrorCode.INVALID_APPLICATION, "No application was found with the provided identifier: %s".formatted(auth.getApplicationId())); + } + case OPERATION_NOT_FOUND, OPERATION_REJECT_FAILURE -> { + logger.info("Operation ID: {} not found or is in unexpected state: {}", request.getRequestObject().getId(), e.getMessage()); + logger.debug("Operation ID: {} not found or is in unexpected state.", request.getRequestObject().getId(), e); + throw new MobileTokenException(ErrorCode.INVALID_OPERATION, "Operation not found or is in an unexpected state"); + } + case INVALID_REQUEST -> { + logger.info("Request validation error: {}", e.getMessage()); + logger.debug("Request validation error.", e); + throw new MobileTokenException(ErrorCode.INVALID_REQUEST, "Request validation error: %s".formatted(e.getMessage())); + } + default -> { + logger.warn("Calling PowerAuth service failed: {}", e.getMessage()); + logger.debug("Calling PowerAuth service failed.", e); + throw new RemoteCommunicationException("Unable to call upstream service."); + } + } } } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java index 80ac42a4..a065ab22 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java @@ -49,6 +49,19 @@ public class DefaultExceptionHandler { return new ErrorResponse("ERROR_GENERIC", "Unknown error occurred while processing request."); } + /** + * Handling of remote communication exception. + * + * @param ex Exception. + * @return Response with error details. + */ + @ExceptionHandler(RemoteCommunicationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public @ResponseBody ErrorResponse handleRemoteExceptionException(RemoteCommunicationException ex) { + logger.warn("Communication with remote system failed", ex); + return new ErrorResponse("REMOTE_COMMUNICATION_ERROR", "Communication with remote system failed."); + } + /** * Exception handler for invalid request exception. * @param ex Exception. diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/RemoteCommunicationException.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/RemoteCommunicationException.java new file mode 100644 index 00000000..34528c1d --- /dev/null +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/RemoteCommunicationException.java @@ -0,0 +1,40 @@ +/* + * PowerAuth Enrollment Server + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.wultra.app.enrollmentserver.errorhandling; + +import java.io.Serial; + +/** + * Exception thrown in case of an error during communication with remote system. + * + * @author Jan Pesek, jan.pesek@wultra.com + */ +public class RemoteCommunicationException extends Exception { + + @Serial + private static final long serialVersionUID = -2565764734609472778L; + + public RemoteCommunicationException(String message) { + super(message); + } + + public RemoteCommunicationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/enumeration/ErrorCode.java b/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/enumeration/ErrorCode.java index a6ef3225..787620ea 100644 --- a/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/enumeration/ErrorCode.java +++ b/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/enumeration/ErrorCode.java @@ -42,6 +42,18 @@ public class ErrorCode { */ public static final String INVALID_ACTIVATION = "INVALID_ACTIVATION"; + /** + * Error code for situation when an invalid application identifier is + * attempted for operation manipulation. + */ + public static final String INVALID_APPLICATION = "INVALID_APPLICATION"; + + /** + * Error code for situation when an invalid operation identifier is + * attempted for operation manipulation. + */ + public static final String INVALID_OPERATION = "INVALID_OPERATION"; + /** * Error code for situation when signature verification fails. */ From 0d6c26f58b6390d92d2e8643bd06247b6095f784 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 06:00:43 +0000 Subject: [PATCH 04/16] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.2.2 to 3.2.3. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.2...v3.2.3) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3e00637..d1b84424 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.2 + 3.2.3 From d929fafadd005fe919877031ad5ca8d57c134824 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Fri, 23 Feb 2024 10:32:50 +0100 Subject: [PATCH 05/16] Fix #1021: Update Docker dependencies - ibm-semeru-runtimes:open-21.0.2_13-jre - Tomcat 10.1.19 --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 16e92997..e2a03d33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ibm-semeru-runtimes:open-21.0.1_12-jre +FROM ibm-semeru-runtimes:open-21.0.2_13-jre LABEL maintainer="petr@wultra.com" # Prepare environment variables @@ -8,7 +8,7 @@ ENV JAVA_HOME=/opt/java/openjdk \ PKG_RELEASE=1~jammy \ TOMCAT_HOME=/usr/local/tomcat \ TOMCAT_MAJOR=10 \ - TOMCAT_VERSION=10.1.17 \ + TOMCAT_VERSION=10.1.19 \ TZ=UTC ENV PATH=$PATH:$LB_HOME:$TOMCAT_HOME/bin @@ -20,7 +20,7 @@ RUN apt-get -y update \ # Install tomcat RUN curl -jkSL -o /tmp/apache-tomcat.tar.gz http://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR}/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz \ - && [ "ff9670f9cd49a604e47edfbcfb5855fe59342048c3278ea8736276b51327adf2d076973f3ad1b8aa7870ef26c28cf7111527be810b445c9927f2a457795f5cb6 /tmp/apache-tomcat.tar.gz" = "$(sha512sum /tmp/apache-tomcat.tar.gz)" ] \ + && [ "7264da6196a510b0bba74469d215d61a464331302239256477f78b6bec067f7f4d90f671b96a440061ae0e20d16b1be8ca1dbd547dab9927383366dbc677f590 /tmp/apache-tomcat.tar.gz" = "$(sha512sum /tmp/apache-tomcat.tar.gz)" ] \ && gunzip /tmp/apache-tomcat.tar.gz \ && tar -C /opt -xf /tmp/apache-tomcat.tar \ && ln -s /opt/apache-tomcat-$TOMCAT_VERSION $TOMCAT_HOME From a604417e80fa9e3e4937f36abfdd018c49790337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 02:12:14 +0000 Subject: [PATCH 06/16] Bump net.javacrumbs.shedlock:shedlock-bom from 5.11.0 to 5.12.0 Bumps [net.javacrumbs.shedlock:shedlock-bom](https://github.com/lukas-krecan/ShedLock) from 5.11.0 to 5.12.0. - [Commits](https://github.com/lukas-krecan/ShedLock/compare/shedlock-parent-5.11.0...shedlock-parent-5.12.0) --- updated-dependencies: - dependency-name: net.javacrumbs.shedlock:shedlock-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d1b84424..54bccb54 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ 7.3.0 - 5.11.0 + 5.12.0 4.0.0 2.2.20 2.3.0 From 8284374edb677eee9babdbbeb3ecf555262b5803 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Mon, 4 Mar 2024 08:54:41 +0100 Subject: [PATCH 07/16] Fix #1028: Duplicate maven-war-plugin declaration --- enrollment-server-onboarding/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/enrollment-server-onboarding/pom.xml b/enrollment-server-onboarding/pom.xml index faca05b8..4e443181 100644 --- a/enrollment-server-onboarding/pom.xml +++ b/enrollment-server-onboarding/pom.xml @@ -200,10 +200,6 @@ - - org.apache.maven.plugins - maven-war-plugin - org.springframework.boot spring-boot-maven-plugin From b27254897e60c7fc39b6f24ee707648ed7ba4b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Wed, 6 Mar 2024 09:31:41 +0100 Subject: [PATCH 08/16] Fix #1025: Java 21 Build Fails (#1030) * Fix #1025: Java 21 Build Fails --- .github/workflows/codeql-analysis.yml | 3 +- .github/workflows/coverity-scan.yml | 1 + .github/workflows/maven-deploy.yml | 2 + .github/workflows/maven-test.yml | 4 +- .../IdentityVerificationRepository.java | 46 +++++++++++-------- .../service/IdentityVerificationService.java | 2 +- .../service/StateMachineService.java | 39 +++++++--------- 7 files changed, 53 insertions(+), 44 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9f62dc96..ae5e8ae7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,4 +18,5 @@ jobs: languages: "['java']" # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin or both - # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both \ No newline at end of file + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + java_version: 21 \ No newline at end of file diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index 8a887f33..e0652fb4 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -14,3 +14,4 @@ jobs: project-name: ${{ github.event.repository.name }} version: ${{ github.sha }} description: ${{ github.ref }} + java_version: 21 diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 9a1690fd..35a0646d 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -34,6 +34,7 @@ jobs: with: environment: internal-publish release_type: snapshot + java_version: 21 secrets: username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} @@ -45,6 +46,7 @@ jobs: with: environment: ${{ inputs.environment }} release_type: ${{ inputs.release_type }} + java_version: 21 secrets: username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml index 6bdada9f..33932bf6 100644 --- a/.github/workflows/maven-test.yml +++ b/.github/workflows/maven-test.yml @@ -15,4 +15,6 @@ on: jobs: maven-tests: uses: wultra/wultra-infrastructure/.github/workflows/maven-test.yml@develop - secrets: inherit \ No newline at end of file + secrets: inherit + with: + java_version: 21 \ No newline at end of file diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/IdentityVerificationRepository.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/IdentityVerificationRepository.java index 51b51a94..a1e19ce6 100644 --- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/IdentityVerificationRepository.java +++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/IdentityVerificationRepository.java @@ -55,28 +55,36 @@ public interface IdentityVerificationRepository extends CrudRepository streamAllIdentityVerificationsToChangeState(); - + Stream streamAllIdentityVerificationsToChangeState(final String documentVerificationProvider); /** * Return identity verification IDs by the given process ID. Include only not yet finished entities. diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationService.java index 907d9ad4..838ef2ad 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationService.java @@ -559,7 +559,7 @@ public VerificationSdkInfo initVerificationSdk(OwnerId ownerId, Map streamAllIdentityVerificationsToChangeState() { - return identityVerificationRepository.streamAllIdentityVerificationsToChangeState(); + return identityVerificationRepository.streamAllIdentityVerificationsToChangeState(identityVerificationConfig.getDocumentVerificationProvider()); } private void moveToDocumentUpload(final OwnerId ownerId, final IdentityVerificationEntity idVerification, final IdentityVerificationStatus status) { diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/service/StateMachineService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/service/StateMachineService.java index 5b135753..56a316f5 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/service/StateMachineService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/service/StateMachineService.java @@ -19,7 +19,6 @@ import com.wultra.app.enrollmentserver.model.integration.OwnerId; import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity; import com.wultra.app.onboardingserver.common.errorhandling.IdentityVerificationException; -import com.wultra.app.onboardingserver.configuration.IdentityVerificationConfig; import com.wultra.app.onboardingserver.impl.service.IdentityVerificationService; import com.wultra.app.onboardingserver.statemachine.EnrollmentStateProvider; import com.wultra.app.onboardingserver.statemachine.consts.EventHeaderName; @@ -69,8 +68,6 @@ public class StateMachineService { private final TransactionTemplate transactionTemplate; - private final IdentityVerificationConfig identityVerificationConfig; - @Transactional public StateMachine processStateMachineEvent(OwnerId ownerId, String processId, OnboardingEvent event) throws IdentityVerificationException { @@ -127,25 +124,23 @@ public Message createMessage(OwnerId ownerId, String processId, public void changeMachineStatesInBatch() { final AtomicInteger countFinished = new AtomicInteger(0); try (Stream stream = identityVerificationService.streamAllIdentityVerificationsToChangeState().parallel()) { - stream.filter(identityVerification -> identityVerification.getDocumentVerifications().stream() - .anyMatch(doc -> identityVerificationConfig.getDocumentVerificationProvider().equals(doc.getProviderName()))) - .forEach(identityVerification -> { - final String processId = identityVerification.getProcessId(); - final OwnerId ownerId = new OwnerId(); - ownerId.setActivationId(identityVerification.getActivationId()); - ownerId.setUserId(identityVerification.getUserId()); - logger.debug("Changing state of machine for process ID: {}", processId); - - transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - transactionTemplate.executeWithoutResult(status -> { - try { - processStateMachineEvent(ownerId, processId, OnboardingEvent.EVENT_NEXT_STATE); - countFinished.incrementAndGet(); - } catch (IdentityVerificationException e) { - logger.warn("Unable to change state for process ID: {}", processId, e); - } - }); - }); + stream.forEach(identityVerification -> { + final String processId = identityVerification.getProcessId(); + final OwnerId ownerId = new OwnerId(); + ownerId.setActivationId(identityVerification.getActivationId()); + ownerId.setUserId(identityVerification.getUserId()); + logger.debug("Changing state of machine for process ID: {}", processId); + + transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + transactionTemplate.executeWithoutResult(status -> { + try { + processStateMachineEvent(ownerId, processId, OnboardingEvent.EVENT_NEXT_STATE); + countFinished.incrementAndGet(); + } catch (IdentityVerificationException e) { + logger.warn("Unable to change state for process ID: {}", processId, e); + } + }); + }); } if (countFinished.get() > 0) { logger.debug("Changed state of {} identity verifications", countFinished.get()); From 4d18520965b70fbc5a9bd2e18c91dd64e414a34e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:58:47 +0000 Subject: [PATCH 09/16] Bump org.openapitools:openapi-generator-maven-plugin from 7.3.0 to 7.4.0 Bumps org.openapitools:openapi-generator-maven-plugin from 7.3.0 to 7.4.0. --- updated-dependencies: - dependency-name: org.openapitools:openapi-generator-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 54bccb54..1fd58b32 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ - 7.3.0 + 7.4.0 5.12.0 4.0.0 From b955363665189f39a810a0b65fafc642501a923a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:59:25 +0000 Subject: [PATCH 10/16] Bump org.springdoc:springdoc-openapi-starter-webmvc-ui Bumps [org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/springdoc/springdoc-openapi/releases) - [Changelog](https://github.com/springdoc/springdoc-openapi/blob/main/CHANGELOG.md) - [Commits](https://github.com/springdoc/springdoc-openapi/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: org.springdoc:springdoc-openapi-starter-webmvc-ui dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 54bccb54..76065942 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ 5.12.0 4.0.0 2.2.20 - 2.3.0 + 2.4.0 1.4.4 1.9.0-SNAPSHOT From 214397e5a395e9ec7d61f44afa3e904ab60a25aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Tue, 19 Mar 2024 07:38:07 +0100 Subject: [PATCH 11/16] Fix #1033: Template detail endpoint (#1034) * Fix #1033: Template detail endpoint --- deploy/enrollment-server.xml | 6 ++ deploy/env.list.tmp | 6 ++ docs/Configuration-Properties.md | 16 ++-- .../response/TemplateDetailResponse.java | 29 ++++++++ enrollment-server/pom.xml | 4 + .../configuration/OpenApiConfiguration.java | 15 +++- .../configuration/SecurityConfig.java | 74 ++++++++++++++++++- .../controller/api/admin/AdminController.java | 69 +++++++++++++++++ .../database/OperationTemplateRepository.java | 4 +- .../service/OperationTemplateService.java | 10 +++ .../main/resources/application-dev.properties | 6 ++ .../src/main/resources/application.properties | 10 +++ 12 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java create mode 100644 enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java diff --git a/deploy/enrollment-server.xml b/deploy/enrollment-server.xml index c9a24edc..8a20f2b6 100644 --- a/deploy/enrollment-server.xml +++ b/deploy/enrollment-server.xml @@ -29,6 +29,12 @@ + + + + + + diff --git a/deploy/env.list.tmp b/deploy/env.list.tmp index 32128f21..8c4ed3da 100644 --- a/deploy/env.list.tmp +++ b/deploy/env.list.tmp @@ -5,6 +5,12 @@ ENROLLMENT_SERVER_PUSH_SERVER_URL= ENROLLMENT_SERVER_MTOKEN_ENABLED=true ENROLLMENT_SERVER_INBOX_ENABLED=true ENROLLMENT_SERVER_ACTIVATION_SPAWN_ENABLED=false +ENROLLMENT_SERVER_ADMIN_ENABLED=false +ENROLLMENT_SERVER_AUTH_TYPE=NONE +ENROLLMENT_SERVER_SECURITY_AUTH_HTTP_BASIC_USER_NAME= +ENROLLMENT_SERVER_SECURITY_AUTH_HTTP_BASIC_USER_PASSWORD= +ENROLLMENT_SERVER_SECURITY_AUTH_OIDC_ISSUER_URI= +ENROLLMENT_SERVER_SECURITY_AUTH_OIDC_AUDIENCES= ENROLLMENT_SERVER_CORRELATION_HEADER_ENABLED=false ENROLLMENT_SERVER_CORRELATION_HEADER_NAME=X-Correlation-ID ENROLLMENT_SERVER_CORRELATION_HEADER_VALUE_VALIDATION_REGEXP=[a-zA-Z0-9\\-]{8,1024} diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index ce1df243..47b9b455 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -29,11 +29,17 @@ The Enrollment Server uses the following public configuration properties: ## Enrollment Server Configuration -| Property | Default | Note | -|---|---|---| -| `enrollment-server.mtoken.enabled` | `true` | Publishing of Mobile Token endpoints can be enabled or disabled using this property. | -| `enrollment-server.inbox.enabled` | `true` | Publishing of Inbox endpoints can be enabled or disabled using this property. | -| `enrollment-server.activation-spawn.enabled` | `false` | The activation spawn functionality can be enabled or disabled using this property. | +| Property | Default | Note | +|---------------------------------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `enrollment-server.mtoken.enabled` | `true` | Publishing of Mobile Token endpoints can be enabled or disabled using this property. | +| `enrollment-server.inbox.enabled` | `true` | Publishing of Inbox endpoints can be enabled or disabled using this property. | +| `enrollment-server.activation-spawn.enabled` | `false` | The activation spawn functionality can be enabled or disabled using this property. | +| `enrollment-server.admin.enabled` | `false` | The admin API can be enabled or disabled using this property. | +| `enrollment-server.auth-type` | `NONE` | `BASIC_HTTP` for basic HTTP authentication or `OIDC` for OpenID Connect. If authentication enabled, the corresponding properties bellow must be configured. | +| `spring.security.user.name` | | Basic HTTP property, user name | +| `spring.security.user.password` | | Basic HTTP property, user password `{id}encodedPassword`, see [Spring Password Storage Format](https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html#authentication-password-storage-dpe-format). | +| `spring.security.oauth2.resource-server.jwt.issuer-uri` | | OIDC property, URL of the provider, e.g. `https://sts.windows.net/example/` | +| `spring.security.oauth2.resource-server.jwt.audiences` | | OIDC property, a comma-separated list of allowed `aud` JWT claim values to be validated. | ## UserInfoProvider Configuration diff --git a/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java new file mode 100644 index 00000000..380eaa08 --- /dev/null +++ b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java @@ -0,0 +1,29 @@ +/* + * PowerAuth Enrollment Server + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.wultra.app.enrollmentserver.api.model.enrollment.response; + +import lombok.Builder; + +/** + * Template detail. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Builder +public record TemplateDetailResponse(String name, String title, String message, String attributes, String language) { +} diff --git a/enrollment-server/pom.xml b/enrollment-server/pom.xml index fae82f69..e844f430 100644 --- a/enrollment-server/pom.xml +++ b/enrollment-server/pom.xml @@ -98,6 +98,10 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + jakarta.servlet diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/OpenApiConfiguration.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/OpenApiConfiguration.java index 69c69a1a..0a562214 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/OpenApiConfiguration.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/OpenApiConfiguration.java @@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.info.License; import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -49,9 +50,11 @@ ) public class OpenApiConfiguration { + private static final String PACKAGE_ADMIN = "com.wultra.app.enrollmentserver.controller.api.admin"; + @Bean public GroupedOpenApi defaultApiGroup() { - String[] packages = { + final String[] packages = { "io.getlime.security.powerauth", "com.wultra.app.enrollmentserver.controller.api" }; @@ -59,6 +62,16 @@ public GroupedOpenApi defaultApiGroup() { return GroupedOpenApi.builder() .group("enrollment-server") .packagesToScan(packages) + .packagesToExclude(PACKAGE_ADMIN) + .build(); + } + + @Bean + @ConditionalOnProperty(value = "enrollment-server.admin.enabled", havingValue = "true") + public GroupedOpenApi adminApiGroup() { + return GroupedOpenApi.builder() + .group("enrollment-server-admin") + .packagesToScan(PACKAGE_ADMIN) .build(); } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/SecurityConfig.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/SecurityConfig.java index 51cdc35f..2b92f3d2 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/SecurityConfig.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/SecurityConfig.java @@ -18,12 +18,26 @@ package com.wultra.app.enrollmentserver.configuration; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.util.Assert; + +import static org.springframework.security.config.Customizer.withDefaults; /** * Spring Security configuration. @@ -32,14 +46,72 @@ */ @Configuration @EnableWebSecurity +@Slf4j public class SecurityConfig { + @Value("${enrollment-server.auth-type}") + private AuthType authType; + @Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { + Assert.state(authType != null, "No authentication type configured."); + + if (authType == AuthType.NONE) { + logger.info("No authentication."); + http.httpBasic(AbstractHttpConfigurer::disable); + } else { + http.authorizeHttpRequests(authorize -> authorize + .requestMatchers(new AntPathRequestMatcher("/api/admin/**")).authenticated() + .anyRequest().permitAll()); + } + + if (authType == AuthType.BASIC_HTTP) { + logger.info("Initializing HTTP basic authentication."); + http.httpBasic(withDefaults()); + } else if (authType == AuthType.OIDC) { + logger.info("Initializing OIDC authentication."); + http.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults())); + } + return http - .httpBasic(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .build(); } + @ConditionalOnProperty(value = "enrollment-server.auth-type", havingValue = "BASIC_HTTP" ) + @Bean + public UserDetailsService userDetailsService(final SecurityProperties securityProperties) { + final String username = securityProperties.getUser().getName(); + Assert.hasLength(username, "Username must not be blank."); + logger.info("Initializing user detail service for: {}", username); + final UserDetails user = User.withUsername(username) + .password(securityProperties.getUser().getPassword()) + .build(); + return new InMemoryUserDetailsManager(user); + } + + @ConditionalOnProperty(value = "enrollment-server.auth-type", havingValue = "BASIC_HTTP" ) + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + enum AuthType { + + /** + * Authentication is turned off. + */ + NONE, + + /** + * Basic HTTP authentication. + */ + BASIC_HTTP, + + /** + * OpenID Connect. + */ + OIDC + } + } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java new file mode 100644 index 00000000..3fb10e6c --- /dev/null +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java @@ -0,0 +1,69 @@ +/* + * PowerAuth Enrollment Server + * Copyright (C) 2024 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.wultra.app.enrollmentserver.controller.api.admin; + +import com.wultra.app.enrollmentserver.api.model.enrollment.response.TemplateDetailResponse; +import com.wultra.app.enrollmentserver.database.entity.OperationTemplateEntity; +import com.wultra.app.enrollmentserver.impl.service.OperationTemplateService; +import io.getlime.core.rest.model.base.response.ObjectResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Admin controller. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@ConditionalOnProperty(value = "enrollment-server.admin.enabled", havingValue = "true") +@RestController +@RequestMapping(value = "api/admin") +@AllArgsConstructor +@Slf4j +public class AdminController { + + private final OperationTemplateService operationTemplateService; + + @GetMapping("/template") + public ObjectResponse> templates() { + logger.debug("Returning template list."); + return new ObjectResponse<>(new ArrayList<>(convert(operationTemplateService.findAll()))); + } + + private static List convert(final List source) { + return source.stream() + .map(AdminController::convert) + .toList(); + } + + private static TemplateDetailResponse convert(final OperationTemplateEntity source) { + return TemplateDetailResponse.builder() + .name(source.getPlaceholder()) + .title(source.getTitle()) + .message(source.getMessage()) + .language(source.getLanguage()) + .attributes(source.getAttributes()) + .build(); + } +} diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/database/OperationTemplateRepository.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/database/OperationTemplateRepository.java index 40c60556..24ffa630 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/database/OperationTemplateRepository.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/database/OperationTemplateRepository.java @@ -19,7 +19,7 @@ package com.wultra.app.enrollmentserver.database; import com.wultra.app.enrollmentserver.database.entity.OperationTemplateEntity; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.ListCrudRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -30,7 +30,7 @@ * @author Petr Dvorak, petr@wultra.com */ @Repository -public interface OperationTemplateRepository extends CrudRepository { +public interface OperationTemplateRepository extends ListCrudRepository { /** * Find an operation template by the given language and operation type. diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/OperationTemplateService.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/OperationTemplateService.java index 9aafce22..d3a93778 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/OperationTemplateService.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/OperationTemplateService.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Optional; /** @@ -59,6 +60,15 @@ public Optional findTemplate(@NotNull String operationT findTemplateFallback(operationType, language)); } + /** + * Find all templates. + * + * @return templates + */ + public List findAll() { + return operationTemplateRepository.findAll(); + } + private Optional findTemplateFallback(final String operationType, final String language) { if (!DEFAULT_LANGUAGE.equals(language)) { logger.debug("Trying fallback to EN locale for operationType={}", operationType); diff --git a/enrollment-server/src/main/resources/application-dev.properties b/enrollment-server/src/main/resources/application-dev.properties index 524c75a0..165c749e 100644 --- a/enrollment-server/src/main/resources/application-dev.properties +++ b/enrollment-server/src/main/resources/application-dev.properties @@ -1,3 +1,9 @@ # Liquibase spring.liquibase.enabled=true spring.liquibase.change-log=classpath:db/changelog/db.changelog-module.xml + +enrollment-server.admin.enabled=true +enrollment-server.auth-type=BASIC_HTTP + +spring.security.user.name=admin +spring.security.user.password={bcrypt}$2a$10$Im45aSJeMpove4pF8/ypB.ufkITjfjpFvby9AMkvy.hrOVixkfkxq diff --git a/enrollment-server/src/main/resources/application.properties b/enrollment-server/src/main/resources/application.properties index 90ccfcb6..195eaa2b 100644 --- a/enrollment-server/src/main/resources/application.properties +++ b/enrollment-server/src/main/resources/application.properties @@ -68,6 +68,16 @@ powerauth.service.security.clientSecret= enrollment-server.mtoken.enabled=true enrollment-server.inbox.enabled=true enrollment-server.activation-spawn.enabled=false +enrollment-server.admin.enabled=false +enrollment-server.auth-type=NONE + +# Basic HTTP Settings +spring.security.user.name= +spring.security.user.password= + +# OIDC Settings +spring.security.oauth2.resource-server.jwt.issuer-uri= +spring.security.oauth2.resource-server.jwt.audiences= # User-info configuration # enrollment-server.user-info.provider= From dbffd9f15f8fc4f2004fb40a1ee16891a812d3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Thu, 21 Mar 2024 07:29:44 +0100 Subject: [PATCH 12/16] Make admin API to return template attributes in structured format (#1037) * Make admin API to return template attributes in a structured format * Refactor response TemplateListResponse to make it consistent with others A follow-up to #1033 --- ...esponse.java => TemplateListResponse.java} | 18 +++++++-- .../controller/api/admin/AdminController.java | 38 ++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) rename enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/{TemplateDetailResponse.java => TemplateListResponse.java} (64%) diff --git a/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateListResponse.java similarity index 64% rename from enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java rename to enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateListResponse.java index 380eaa08..248f0e42 100644 --- a/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateDetailResponse.java +++ b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateListResponse.java @@ -18,12 +18,24 @@ package com.wultra.app.enrollmentserver.api.model.enrollment.response; import lombok.Builder; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.util.ArrayList; +import java.util.List; /** - * Template detail. + * Template list response. * * @author Lubos Racansky, lubos.racansky@wultra.com */ -@Builder -public record TemplateDetailResponse(String name, String title, String message, String attributes, String language) { +@EqualsAndHashCode(callSuper = true) +public class TemplateListResponse extends ArrayList { + + @Serial + private static final long serialVersionUID = -5446919236567435144L; + + @Builder + public record TemplateDetail(String name, String title, String message, List attributes, String language) { + } } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java index 3fb10e6c..b1a9e883 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java @@ -17,18 +17,21 @@ */ package com.wultra.app.enrollmentserver.controller.api.admin; -import com.wultra.app.enrollmentserver.api.model.enrollment.response.TemplateDetailResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.wultra.app.enrollmentserver.api.model.enrollment.response.TemplateListResponse; import com.wultra.app.enrollmentserver.database.entity.OperationTemplateEntity; import com.wultra.app.enrollmentserver.impl.service.OperationTemplateService; import io.getlime.core.rest.model.base.response.ObjectResponse; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; import java.util.List; /** @@ -45,25 +48,42 @@ public class AdminController { private final OperationTemplateService operationTemplateService; + private final ObjectMapper objectMapper; + @GetMapping("/template") - public ObjectResponse> templates() { + public ObjectResponse templates() { logger.debug("Returning template list."); - return new ObjectResponse<>(new ArrayList<>(convert(operationTemplateService.findAll()))); + final TemplateListResponse response = new TemplateListResponse(); + response.addAll(convert(operationTemplateService.findAll())); + return new ObjectResponse<>(response); } - private static List convert(final List source) { + private List convert(final List source) { return source.stream() - .map(AdminController::convert) + .map(this::convert) .toList(); } - private static TemplateDetailResponse convert(final OperationTemplateEntity source) { - return TemplateDetailResponse.builder() + private TemplateListResponse.TemplateDetail convert(final OperationTemplateEntity source) { + return TemplateListResponse.TemplateDetail.builder() .name(source.getPlaceholder()) .title(source.getTitle()) .message(source.getMessage()) .language(source.getLanguage()) - .attributes(source.getAttributes()) + .attributes(convert(source.getAttributes())) .build(); } + + private List convert(final String source) { + if (!StringUtils.hasText(source)) { + return null; + } + + try { + return objectMapper.readValue(source, new TypeReference<>() {}); + } catch (JsonProcessingException e) { + logger.warn("Unable to convert attributes, returning an empty collection", e); + return List.of(); + } + } } From c1671534b598d8685a3c16cea7a0a0a0bf9b6e44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 02:49:53 +0000 Subject: [PATCH 13/16] Bump io.swagger.core.v3:swagger-annotations-jakarta Bumps io.swagger.core.v3:swagger-annotations-jakarta from 2.2.20 to 2.2.21. --- updated-dependencies: - dependency-name: io.swagger.core.v3:swagger-annotations-jakarta dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 799364da..ee4f4c9b 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ 5.12.0 4.0.0 - 2.2.20 + 2.2.21 2.4.0 1.4.4 From dae3baf7e92e47228342b02403a37935ff55fef0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 02:50:03 +0000 Subject: [PATCH 14/16] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 799364da..a4b24b27 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.3 + 3.2.4 From 8aad7d604bfa679edb720f25a34e5a0369b16efc Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Thu, 28 Mar 2024 07:31:26 +0100 Subject: [PATCH 15/16] Downgrade JDK for Coverity GHA to 17 --- .github/workflows/coverity-scan.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index e0652fb4..8a887f33 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -14,4 +14,3 @@ jobs: project-name: ${{ github.event.repository.name }} version: ${{ github.sha }} description: ${{ github.ref }} - java_version: 21 From ba1f3376ee222b90a55165d6767a578ad02c75d2 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Thu, 28 Mar 2024 08:23:52 +0100 Subject: [PATCH 16/16] Fix #1041: 404 error is propagated as 500 --- .../errorhandling/DefaultExceptionHandler.java | 15 +++++++++++++++ .../errorhandling/DefaultExceptionHandler.java | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/errorhandling/DefaultExceptionHandler.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/errorhandling/DefaultExceptionHandler.java index 94b07ebf..ae2667f9 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/errorhandling/DefaultExceptionHandler.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/errorhandling/DefaultExceptionHandler.java @@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.resource.NoResourceFoundException; /** * Exception handler for RESTful API issues. @@ -236,4 +237,18 @@ public class DefaultExceptionHandler { logger.warn("Error occurred.", e); return new ErrorResponse("INVALID_REQUEST", "Invalid request sent."); } + + /** + * Exception handler for no resource found. + * + * @param e Exception. + * @return Response with error details. + */ + @ExceptionHandler(NoResourceFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public @ResponseBody ErrorResponse handleNoResourceFoundException(final NoResourceFoundException e) { + logger.warn("Error occurred when calling an API: {}", e.getMessage()); + logger.debug("Exception detail: ", e); + return new ErrorResponse("ERROR_NOT_FOUND", "Resource not found."); + } } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java index a065ab22..e85b9522 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.resource.NoResourceFoundException; /** * Exception handler for RESTful API issues. @@ -158,4 +159,17 @@ public class DefaultExceptionHandler { return new ErrorResponse("INBOX_FAILED", "Unable to process inbox request."); } + /** + * Exception handler for no resource found. + * + * @param e Exception. + * @return Response with error details. + */ + @ExceptionHandler(NoResourceFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public @ResponseBody ErrorResponse handleNoResourceFoundException(final NoResourceFoundException e) { + logger.warn("Error occurred when calling an API: {}", e.getMessage()); + logger.debug("Exception detail: ", e); + return new ErrorResponse("ERROR_NOT_FOUND", "Resource not found."); + } }