From 70767509b623118d43bcd542867192b9c875965d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Wed, 7 Feb 2024 10:00:01 +0100 Subject: [PATCH] Merge upstream (#137) --- docs/Configuration-Properties.md | 5 +- docs/onboarding/Configuration-Properties.md | 7 +- .../Configuration-Verification-Providers.md | 41 +++++++++- enrollment-server-api-model/pom.xml | 7 +- .../request/PushRegisterRequest.java | 30 ++++++- .../pom.xml | 2 +- .../pom.xml | 2 +- enrollment-server-onboarding-api/pom.xml | 2 +- enrollment-server-onboarding-common/pom.xml | 2 +- .../resources/application-test.properties | 1 - .../pom.xml | 15 +++- .../model/integration/OwnerId.java | 8 +- .../model/integration/OwnerIdTest.java | 40 ++++++++++ .../pom.xml | 2 +- .../innovatrics/InnovatricsApiService.java | 8 -- ...novatricsDocumentVerificationProvider.java | 13 ++- .../InnovatricsLivenessService.java | 3 +- .../InnovatricsPresenceCheckProvider.java | 4 +- .../pom.xml | 2 +- .../iproov/IProovPresenceCheckProvider.java | 4 +- .../pom.xml | 2 +- .../ZenidDocumentVerificationProvider.java | 8 +- .../provider/zenid/ZenidRestApiService.java | 9 +-- enrollment-server-onboarding/pom.xml | 17 +++- .../IdentityVerificationConfig.java | 3 + ...ultraMockDocumentVerificationProvider.java | 17 ++-- .../impl/service/ClientEvaluationService.java | 39 +++++++-- .../service/IdentityVerificationService.java | 23 ++---- .../impl/service/ImageProcessor.java | 11 ++- .../impl/service/PresenceCheckService.java | 7 +- .../document/DocumentProcessingService.java | 3 +- .../util/ConditionalOnPropertyNotEmpty.java | 4 +- .../model/request/EvaluateClientRequest.java | 9 +++ .../rest/ClientEvaluateRequestDto.java | 7 ++ .../provider/rest/RestOnboardingProvider.java | 2 + .../statemachine/util/StateContextUtil.java | 4 +- .../task/cleaning/CleaningService.java | 31 ++++++-- .../src/main/resources/application.properties | 4 +- .../service/ClientEvaluationServiceTest.java | 19 ++++- .../IdentityVerificationServiceTest.java | 57 +++++++++++++ .../task/cleaning/CleaningServiceTest.java | 29 +++++++ .../resources/application-test.properties | 1 - enrollment-server/pom.xml | 12 ++- .../impl/service/PushRegistrationService.java | 17 ++-- .../util/ConditionalOnPropertyNotEmpty.java | 4 +- .../PushRegisterRequestValidator.java | 6 +- .../src/main/resources/application.properties | 5 +- .../converter/MobileTokenConverterTest.java | 79 +++++++++---------- mtoken-model/pom.xml | 2 +- pom.xml | 42 ++-------- 50 files changed, 468 insertions(+), 203 deletions(-) create mode 100644 enrollment-server-onboarding-domain-model/src/test/java/com/wultra/app/enrollmentserver/model/integration/OwnerIdTest.java diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index 46718a26..ce1df243 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -9,7 +9,6 @@ The Enrollment Server uses the following public configuration properties: | `spring.datasource.url` | `_empty_` | Database JDBC URL | | `spring.datasource.username` | `_empty_` | Database JDBC username | | `spring.datasource.password` | `_empty_` | Database JDBC password | -| `spring.datasource.driver-class-name` | `_empty_` | Datasource JDBC class name | | `spring.jpa.hibernate.ddl-auto` | `none` | Configuration of automatic database schema creation | | `spring.jpa.properties.hibernate.connection.characterEncoding` | `_empty_` | Character encoding | | `spring.jpa.properties.hibernate.connection.useUnicode` | `_empty_` | Character encoding - Unicode support | @@ -63,6 +62,8 @@ logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS ## Monitoring and Observability - +| Property | Default | Note | +|-------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `management.tracing.sampling.probability` | `1.0` | Specifies the proportion of requests that are sampled for tracing. A value of 1.0 means that 100% of requests are sampled, while a value of 0 effectively disables tracing. | The WAR file includes the `micrometer-registry-prometheus` dependency. Discuss its configuration with the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/3.1.x/reference/html/actuator.html#actuator.metrics). diff --git a/docs/onboarding/Configuration-Properties.md b/docs/onboarding/Configuration-Properties.md index 799d45a1..3f1881e6 100644 --- a/docs/onboarding/Configuration-Properties.md +++ b/docs/onboarding/Configuration-Properties.md @@ -9,7 +9,6 @@ The Onboarding Server uses the following public configuration properties: | `spring.datasource.url` | `jdbc:postgresql://localhost:5432/powerauth` | Database JDBC URL | | `spring.datasource.username` | `powerauth` | Database JDBC username | | `spring.datasource.password` | `_empty_` | Database JDBC password | -| `spring.datasource.driver-class-name` | `org.postgresql.Driver` | Datasource JDBC class name | | `spring.jpa.hibernate.ddl-auto` | `none` | Configuration of automatic database schema creation | | `spring.jpa.properties.hibernate.connection.characterEncoding` | `utf8` | Character encoding | | `spring.jpa.properties.hibernate.connection.useUnicode` | `true` | Character encoding - Unicode support | @@ -46,7 +45,6 @@ The Onboarding Server uses the following public configuration properties: | `enrollment-server-onboarding.identity-verification.otp.enabled` | `true` | Whether OTP verification is enabled during identity verification. | | `enrollment-server-onboarding.identity-verification.max-failed-attempts` | `5` | Maximum failed attempts for identity verification. | | `enrollment-server-onboarding.identity-verification.max-failed-attempts-document-upload` | `5` | Maximum failed attempts for document upload. | -| `enrollment-server-onboarding.client-evaluation.max-failed-attempts` | `5` | Maximum failed attempts for client evaluation. | ## Digital Onboarding Adapter Configuration @@ -69,6 +67,7 @@ The Onboarding Server uses the following public configuration properties: | Property | Default | Note | |---|---|---| | `enrollment-server-onboarding.client-evaluation.max-failed-attempts` | 5 | Number of maximum failed attempts for client evaluation. | +| `enrollment-server-onboarding.client-evaluation.include-extracted-data` | `false` | Include extracted data to the evaluate client request. The format of extracted data is defined by the provider of document verification. | ## Document Verification Provider Configuration @@ -170,6 +169,8 @@ logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS ## Monitoring and Observability - +| Property | Default | Note | +|-------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `management.tracing.sampling.probability` | `1.0` | Specifies the proportion of requests that are sampled for tracing. A value of 1.0 means that 100% of requests are sampled, while a value of 0 effectively disables tracing. | The WAR file includes the `micrometer-registry-prometheus` dependency. Discuss its configuration with the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/3.1.x/reference/html/actuator.html#actuator.metrics). diff --git a/docs/onboarding/Configuration-Verification-Providers.md b/docs/onboarding/Configuration-Verification-Providers.md index ed358b8b..09d04519 100644 --- a/docs/onboarding/Configuration-Verification-Providers.md +++ b/docs/onboarding/Configuration-Verification-Providers.md @@ -11,7 +11,7 @@ The document verification process is currently supported for following providers ### ZenID -#### Configuration - API key +#### API key The authorization of all API calls is secured by an API key value. It has to be sent as the `Authorization: api_key VALUE` header value. Check the bottom of the `Manual/Configuration` page for more details. @@ -21,7 +21,7 @@ The API key value can be configured/get from the `Access` page configuration: - Condition: `ApiKeyEqualsValue` - Value: the value here is the value of the API key -#### Configuration - Validators +#### Validators It is recommended to create a custom validation profile. The sensitivity of selected validators can be tuned-up or disabled completely at the `Sensitivity` page. The profile can be then set as the default or specified in the configuration properties. @@ -32,6 +32,41 @@ When calling `document-verification/init-sdk` following implementation fields ar - Init token - send a token value `sdk-init-token` in the request body `attributes` map field - SDK response - receive the value under `zenid-sdk-init-response` from the response `attributes` map field +### Innovatrics + +Innovatrics documentation for developers can be found at [this link](https://developers.innovatrics.com/digital-onboarding/technical/remote/dot-dis/latest/documentation/). + +#### OCR Threshold + +During a document validation Innovatrics provides a list of fields extracted from the document, that have OCR +confidence lower than configurable threshold. If the list is not empty, there is a high probability that some +information is read incorrectly. For that reason, this document will be rejected. The OCR confidence threshold is `0.92` +by default, and can be tuned using `innovatrics.dot.dis.customer.document.inspection.ocr-text-field-threshold`. + +#### Text Consistency + +For each document Innovatrics tries to read visual zone, machine-readable zone and barcode. These isolated parts are +cross-checked during a document validation by Innovatrics. If there are inconsistency between visual zone and +machine-readable zone, or between visual-zone and barcode, the document will be rejected. However, some editions of +identification documents are inconsistent by design. To prevent false rejection of those document modify the +configuration. +Following example excludes `issuingAuthority` field of Czech identity card 2005 edition from text consistency check: + +```yml +innovatrics: + dot: + dis: + customer: + document: + inspection: + text-consistency-check: + CZE_identity-card_2005-01-01: + exclusions: + - issuingAuthority +``` + +The format of the document name is `{country}_{type}_{edition}` according to the response of `/metadata` request. + ## Presence Check The document verification process is currently supported for following providers: @@ -39,7 +74,7 @@ The document verification process is currently supported for following providers - [Innovatrics](https://www.innovatrics.com/) - use value `innovatrics` in configuration - Mock - useful for simple testing and local runs - use value `mock` in configuration -#### Configuration +### iProov There are a few needed configuration changes to bring a successful integration. All the following configuration tuning has to be requested from the iProov's [support team](https://iproov.freshdesk.com/support/login) on a per-service basis: diff --git a/enrollment-server-api-model/pom.xml b/enrollment-server-api-model/pom.xml index a798a371..ec7badbe 100644 --- a/enrollment-server-api-model/pom.xml +++ b/enrollment-server-api-model/pom.xml @@ -30,7 +30,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT @@ -43,6 +43,11 @@ io.swagger.core.v3 swagger-annotations-jakarta + + + com.fasterxml.jackson.core + jackson-annotations + diff --git a/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/request/PushRegisterRequest.java b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/request/PushRegisterRequest.java index 21dee883..c42ed56e 100644 --- a/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/request/PushRegisterRequest.java +++ b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/request/PushRegisterRequest.java @@ -18,19 +18,41 @@ package com.wultra.app.enrollmentserver.api.model.enrollment.request; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; +import lombok.ToString; /** - * Class representing a device registration request. The supported platform - * values are 'ios' and 'android'. The push token is the value received from - * APNS or FCM services without any modification. + * Class representing a device registration request. * * @author Petr Dvorak, petr@wultra.com */ @Data public class PushRegisterRequest { - private String platform; + /** + * The platform. + */ + @NotNull + private Platform platform; + + /** + * The push token is the value received from APNS or FCM services without any modification. + */ + @NotBlank + @ToString.Exclude + @Schema(description = "The push token is the value received from APNS or FCM services without any modification.") private String token; + public enum Platform { + @JsonProperty("ios") + IOS, + + @JsonProperty("android") + ANDROID + } + } diff --git a/enrollment-server-onboarding-adapter-mock/pom.xml b/enrollment-server-onboarding-adapter-mock/pom.xml index 99f5897a..b80b2924 100644 --- a/enrollment-server-onboarding-adapter-mock/pom.xml +++ b/enrollment-server-onboarding-adapter-mock/pom.xml @@ -24,7 +24,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT enrollment-server-onboarding-adapter-mock diff --git a/enrollment-server-onboarding-api-model/pom.xml b/enrollment-server-onboarding-api-model/pom.xml index b22e220c..fcc57e0b 100644 --- a/enrollment-server-onboarding-api-model/pom.xml +++ b/enrollment-server-onboarding-api-model/pom.xml @@ -7,7 +7,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT enrollment-server-onboarding-api-model diff --git a/enrollment-server-onboarding-api/pom.xml b/enrollment-server-onboarding-api/pom.xml index 48cb44c2..40396bfc 100644 --- a/enrollment-server-onboarding-api/pom.xml +++ b/enrollment-server-onboarding-api/pom.xml @@ -25,7 +25,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT com.wultra.security diff --git a/enrollment-server-onboarding-common/pom.xml b/enrollment-server-onboarding-common/pom.xml index d6d88b13..2cc80fe4 100644 --- a/enrollment-server-onboarding-common/pom.xml +++ b/enrollment-server-onboarding-common/pom.xml @@ -24,7 +24,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT enrollment-server-onboarding-common diff --git a/enrollment-server-onboarding-common/src/test/resources/application-test.properties b/enrollment-server-onboarding-common/src/test/resources/application-test.properties index 308c2b3a..98bb47ca 100644 --- a/enrollment-server-onboarding-common/src/test/resources/application-test.properties +++ b/enrollment-server-onboarding-common/src/test/resources/application-test.properties @@ -1,5 +1,4 @@ spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE spring.datasource.username=sa spring.datasource.password=password -spring.datasource.driver-class-name=org.h2.Driver spring.jpa.hibernate.ddl-auto=create diff --git a/enrollment-server-onboarding-domain-model/pom.xml b/enrollment-server-onboarding-domain-model/pom.xml index 05d934ce..2f5edb9e 100644 --- a/enrollment-server-onboarding-domain-model/pom.xml +++ b/enrollment-server-onboarding-domain-model/pom.xml @@ -30,7 +30,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT @@ -38,6 +38,19 @@ io.getlime.security powerauth-java-crypto + + + + org.bouncycastle + bcprov-jdk18on + + + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/enrollment-server-onboarding-domain-model/src/main/java/com/wultra/app/enrollmentserver/model/integration/OwnerId.java b/enrollment-server-onboarding-domain-model/src/main/java/com/wultra/app/enrollmentserver/model/integration/OwnerId.java index dc5df814..3992bfef 100644 --- a/enrollment-server-onboarding-domain-model/src/main/java/com/wultra/app/enrollmentserver/model/integration/OwnerId.java +++ b/enrollment-server-onboarding-domain-model/src/main/java/com/wultra/app/enrollmentserver/model/integration/OwnerId.java @@ -17,13 +17,14 @@ */ package com.wultra.app.enrollmentserver.model.integration; -import com.google.common.io.BaseEncoding; import io.getlime.security.powerauth.crypto.lib.util.Hash; import lombok.AccessLevel; import lombok.Data; import lombok.Setter; import lombok.ToString; +import org.bouncycastle.util.encoders.Base32; +import java.nio.charset.StandardCharsets; import java.util.Date; /** @@ -73,9 +74,8 @@ public String getUserIdSecured() { throw new IllegalStateException("Missing userId value"); } if (userIdSecured == null) { - userIdSecured = BaseEncoding.base32() - .omitPadding() - .encode(Hash.sha256(userId)); + userIdSecured = new String(Base32.encode(Hash.sha256(userId)), StandardCharsets.UTF_8) + .replace("=", ""); if (userIdSecured.length() > USER_ID_MAX_LENGTH) { userIdSecured = userIdSecured.substring(0, USER_ID_MAX_LENGTH); } diff --git a/enrollment-server-onboarding-domain-model/src/test/java/com/wultra/app/enrollmentserver/model/integration/OwnerIdTest.java b/enrollment-server-onboarding-domain-model/src/test/java/com/wultra/app/enrollmentserver/model/integration/OwnerIdTest.java new file mode 100644 index 00000000..b763f651 --- /dev/null +++ b/enrollment-server-onboarding-domain-model/src/test/java/com/wultra/app/enrollmentserver/model/integration/OwnerIdTest.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.model.integration; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test for {@link OwnerId}. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +class OwnerIdTest { + + @Test + void testUserIdSecured() { + final OwnerId tested = new OwnerId(); + tested.setUserId("Joe"); + + final String result = tested.getUserIdSecured(); + + assertEquals("NXMLPV6TYXCGRGZT4UNZ6EF4NKN6RH7I7IVBE7EMNQB42BOWRLHA", result); + } +} diff --git a/enrollment-server-onboarding-provider-innovatrics/pom.xml b/enrollment-server-onboarding-provider-innovatrics/pom.xml index bec000cb..629a295f 100644 --- a/enrollment-server-onboarding-provider-innovatrics/pom.xml +++ b/enrollment-server-onboarding-provider-innovatrics/pom.xml @@ -25,7 +25,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT com.wultra.security diff --git a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsApiService.java b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsApiService.java index 916c08fb..bc3caebd 100644 --- a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsApiService.java +++ b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsApiService.java @@ -222,14 +222,6 @@ public CreateSelfieResponse createSelfie(final String customerId, final String l } } - // TODO remove - temporal test call -// @PostConstruct -// public void testCall() throws RestClientException { -// logger.info("Trying a test call"); -// final ResponseEntity response = restClient.get("/api/v1/metadata", STRING_TYPE_REFERENCE); -// logger.info("Result of test call: {}", response.getBody()); -// } - /** * Create a new customer resource. * @param ownerId owner identification. diff --git a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsDocumentVerificationProvider.java b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsDocumentVerificationProvider.java index 5f660561..d2f6a81d 100644 --- a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsDocumentVerificationProvider.java +++ b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsDocumentVerificationProvider.java @@ -20,11 +20,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; import com.wultra.app.enrollmentserver.model.enumeration.DocumentType; import com.wultra.app.enrollmentserver.model.enumeration.DocumentVerificationStatus; -import com.wultra.app.enrollmentserver.model.integration.*; import com.wultra.app.enrollmentserver.model.integration.Image; +import com.wultra.app.enrollmentserver.model.integration.*; import com.wultra.app.onboardingserver.api.errorhandling.DocumentVerificationException; import com.wultra.app.onboardingserver.api.provider.DocumentVerificationProvider; import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; @@ -33,10 +32,10 @@ import com.wultra.app.onboardingserver.provider.innovatrics.model.api.*; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import java.util.*; import java.util.stream.Collectors; @@ -99,7 +98,7 @@ public DocumentsSubmitResult submitDocuments(OwnerId id, List } final Optional primaryPage = results.getResults().stream() - .filter(result -> Strings.isNullOrEmpty(result.getRejectReason()) && Strings.isNullOrEmpty(result.getErrorDetail())) + .filter(result -> StringUtils.isBlank(result.getRejectReason()) && StringUtils.isBlank(result.getErrorDetail())) .findFirst(); if (primaryPage.isPresent()) { @@ -132,9 +131,9 @@ public DocumentsVerificationResult verifyDocuments(OwnerId id, List uplo final String rejectReasons = results.getResults().stream() .map(DocumentVerificationResult::getRejectReason) - .filter(StringUtils::hasText) + .filter(StringUtils::isNotBlank) .collect(Collectors.joining(";")); - if (StringUtils.hasText(rejectReasons)) { + if (StringUtils.isNotBlank(rejectReasons)) { logger.debug("Some documents were rejected: rejectReasons={}, {}", rejectReasons, id); results.setStatus(DocumentVerificationStatus.REJECTED); results.setRejectReason(rejectReasons); @@ -172,7 +171,7 @@ public void cleanupDocuments(OwnerId id, List uploadIds) throws RemoteCo public List parseRejectionReasons(DocumentResultEntity docResult) throws DocumentVerificationException { logger.debug("Parsing rejection reasons of {}", docResult); final String rejectionReasons = docResult.getRejectReason(); - if (!StringUtils.hasText(rejectionReasons)) { + if (StringUtils.isBlank(rejectionReasons)) { return Collections.emptyList(); } diff --git a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsLivenessService.java b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsLivenessService.java index 8b97aa52..cdf5061c 100644 --- a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsLivenessService.java +++ b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsLivenessService.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; import com.wultra.app.enrollmentserver.model.integration.OwnerId; import com.wultra.app.enrollmentserver.model.integration.SessionInfo; import com.wultra.app.onboardingserver.common.database.IdentityVerificationRepository; @@ -125,7 +124,7 @@ private static String fetchCustomerId(final OwnerId id, final IdentityVerificati } final String customerId = (String) sessionInfo.getSessionAttributes().get(SessionInfo.ATTRIBUTE_PRIMARY_DOCUMENT_REFERENCE); - if (Strings.isNullOrEmpty(customerId)) { + if (StringUtils.isBlank(customerId)) { throw new IdentityVerificationException("Missing a customer ID value for calling Innovatrics, " + id); } return customerId; diff --git a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsPresenceCheckProvider.java b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsPresenceCheckProvider.java index 007c06c6..b4dca0c5 100644 --- a/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsPresenceCheckProvider.java +++ b/enrollment-server-onboarding-provider-innovatrics/src/main/java/com/wultra/app/onboardingserver/provider/innovatrics/InnovatricsPresenceCheckProvider.java @@ -17,7 +17,6 @@ */ package com.wultra.app.onboardingserver.provider.innovatrics; -import com.google.common.base.Strings; import com.wultra.app.enrollmentserver.model.enumeration.PresenceCheckStatus; import com.wultra.app.enrollmentserver.model.integration.Image; import com.wultra.app.enrollmentserver.model.integration.OwnerId; @@ -31,6 +30,7 @@ import com.wultra.app.onboardingserver.provider.innovatrics.model.api.SelfieSimilarityWith; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @@ -151,7 +151,7 @@ private static Optional fail(final String errorDetail) { private static String fetchCustomerId(final OwnerId id, final SessionInfo sessionInfo) throws PresenceCheckException { final String customerId = (String) sessionInfo.getSessionAttributes().get(SessionInfo.ATTRIBUTE_PRIMARY_DOCUMENT_REFERENCE); - if (Strings.isNullOrEmpty(customerId)) { + if (StringUtils.isBlank(customerId)) { throw new PresenceCheckException("Missing a customer ID value for calling Innovatrics, " + id); } return customerId; diff --git a/enrollment-server-onboarding-provider-iproov/pom.xml b/enrollment-server-onboarding-provider-iproov/pom.xml index 3822d8b3..b7cbfe87 100644 --- a/enrollment-server-onboarding-provider-iproov/pom.xml +++ b/enrollment-server-onboarding-provider-iproov/pom.xml @@ -25,7 +25,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT com.wultra.security diff --git a/enrollment-server-onboarding-provider-iproov/src/main/java/com/wultra/app/onboardingserver/provider/iproov/IProovPresenceCheckProvider.java b/enrollment-server-onboarding-provider-iproov/src/main/java/com/wultra/app/onboardingserver/provider/iproov/IProovPresenceCheckProvider.java index d3457922..2521216b 100644 --- a/enrollment-server-onboarding-provider-iproov/src/main/java/com/wultra/app/onboardingserver/provider/iproov/IProovPresenceCheckProvider.java +++ b/enrollment-server-onboarding-provider-iproov/src/main/java/com/wultra/app/onboardingserver/provider/iproov/IProovPresenceCheckProvider.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; import com.wultra.app.enrollmentserver.model.enumeration.PresenceCheckStatus; import com.wultra.app.enrollmentserver.model.integration.Image; import com.wultra.app.enrollmentserver.model.integration.OwnerId; @@ -34,6 +33,7 @@ import com.wultra.app.onboardingserver.provider.iproov.model.api.EnrolResponse; import com.wultra.core.rest.client.base.RestClientException; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -161,7 +161,7 @@ public SessionInfo startPresenceCheck(OwnerId id) throws PresenceCheckException, @Override public PresenceCheckResult getResult(OwnerId id, SessionInfo sessionInfo) throws PresenceCheckException, RemoteCommunicationException { final String token = (String) sessionInfo.getSessionAttributes().get(VERIFICATION_TOKEN); - if (Strings.isNullOrEmpty(token)) { + if (StringUtils.isBlank(token)) { throw new PresenceCheckException("Missing a token value for verification validation in iProov, " + id); } diff --git a/enrollment-server-onboarding-provider-zenid/pom.xml b/enrollment-server-onboarding-provider-zenid/pom.xml index 00c1a27c..80f1bb3d 100644 --- a/enrollment-server-onboarding-provider-zenid/pom.xml +++ b/enrollment-server-onboarding-provider-zenid/pom.xml @@ -25,7 +25,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT com.wultra.security diff --git a/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidDocumentVerificationProvider.java b/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidDocumentVerificationProvider.java index 4dc71cfc..3571c78e 100644 --- a/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidDocumentVerificationProvider.java +++ b/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidDocumentVerificationProvider.java @@ -20,22 +20,22 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Preconditions; import com.wultra.app.enrollmentserver.model.enumeration.CardSide; import com.wultra.app.enrollmentserver.model.enumeration.DocumentType; import com.wultra.app.enrollmentserver.model.enumeration.DocumentVerificationStatus; import com.wultra.app.enrollmentserver.model.integration.*; +import com.wultra.app.onboardingserver.api.errorhandling.DocumentVerificationException; +import com.wultra.app.onboardingserver.api.provider.DocumentVerificationProvider; import com.wultra.app.onboardingserver.common.database.DocumentVerificationRepository; import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.common.errorhandling.RemoteCommunicationException; import com.wultra.app.onboardingserver.provider.zenid.model.api.*; -import com.wultra.app.onboardingserver.api.errorhandling.DocumentVerificationException; -import com.wultra.app.onboardingserver.api.provider.DocumentVerificationProvider; import com.wultra.core.rest.client.base.RestClientException; import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -336,7 +336,7 @@ public List parseRejectionReasons(DocumentResultEntity docResult) throws @Override public VerificationSdkInfo initVerificationSdk(OwnerId id, Map initAttributes) throws RemoteCommunicationException, DocumentVerificationException { - Preconditions.checkArgument(initAttributes.containsKey(SDK_INIT_TOKEN), "Missing initialization token for ZenID SDK"); + Validate.isTrue(initAttributes.containsKey(SDK_INIT_TOKEN), "Missing initialization token for ZenID SDK"); String token = initAttributes.get(SDK_INIT_TOKEN); ResponseEntity responseEntity; diff --git a/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidRestApiService.java b/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidRestApiService.java index c98964a2..200cf86f 100644 --- a/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidRestApiService.java +++ b/enrollment-server-onboarding-provider-zenid/src/main/java/com/wultra/app/onboardingserver/provider/zenid/ZenidRestApiService.java @@ -17,7 +17,6 @@ */ package com.wultra.app.onboardingserver.provider.zenid; -import com.google.common.base.Preconditions; import com.wultra.app.enrollmentserver.model.enumeration.CardSide; import com.wultra.app.enrollmentserver.model.enumeration.DocumentType; import com.wultra.app.enrollmentserver.model.integration.OwnerId; @@ -27,6 +26,7 @@ import com.wultra.core.rest.client.base.RestClientException; import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -104,7 +104,7 @@ public ZenidRestApiService( */ public ResponseEntity uploadSample(OwnerId ownerId, SubmittedDocument document) throws RestClientException { - Preconditions.checkNotNull(document.getPhoto(), "Missing photo in " + document); + Validate.notNull(document.getPhoto(), "Missing photo in " + document); final MultiValueMap queryParams = buildQueryParams(ownerId, document); @@ -148,9 +148,8 @@ public ResponseEntity syncSample(String documentId * @param sampleIds Ids of previously uploaded samples. * @return Response entity with the investigation result */ - public ResponseEntity investigateSamples(List sampleIds) - throws RestClientException { - Preconditions.checkArgument(sampleIds.size() > 0, "Missing sample ids for investigation"); + public ResponseEntity investigateSamples(List sampleIds) throws RestClientException { + Validate.notEmpty(sampleIds, "Missing sample ids for investigation"); MultiValueMap queryParams = new LinkedMultiValueMap<>(); sampleIds.forEach(sampleId -> queryParams.add("sampleIDs", sampleId)); diff --git a/enrollment-server-onboarding/pom.xml b/enrollment-server-onboarding/pom.xml index 3eed482d..1d883cea 100644 --- a/enrollment-server-onboarding/pom.xml +++ b/enrollment-server-onboarding/pom.xml @@ -29,7 +29,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT @@ -101,6 +101,11 @@ spring-boot-starter-validation + + com.github.ben-manes.caffeine + caffeine + + jakarta.servlet jakarta.servlet-api @@ -148,6 +153,16 @@ micrometer-registry-prometheus + + io.projectreactor + reactor-core-micrometer + + + + io.micrometer + micrometer-tracing-bridge-otel + + org.springframework.boot diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java index 37e1d40b..379db3cd 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java @@ -79,6 +79,9 @@ public class IdentityVerificationConfig { @Value("${enrollment-server-onboarding.client-evaluation.max-failed-attempts:5}") private int clientEvaluationMaxFailedAttempts; + @Value("${enrollment-server-onboarding.client-evaluation.include-extracted-data:false}") + private boolean sendingExtractedDataEnabled; + @PostConstruct void validate() { // Once in the future, we may replace OTP in SCA by NFC document reading diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/docverify/mock/provider/WultraMockDocumentVerificationProvider.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/docverify/mock/provider/WultraMockDocumentVerificationProvider.java index 69283098..b4b384d6 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/docverify/mock/provider/WultraMockDocumentVerificationProvider.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/docverify/mock/provider/WultraMockDocumentVerificationProvider.java @@ -17,18 +17,18 @@ */ package com.wultra.app.onboardingserver.docverify.mock.provider; -import com.google.common.base.Ascii; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.wultra.app.enrollmentserver.model.enumeration.DocumentType; import com.wultra.app.enrollmentserver.model.enumeration.DocumentVerificationStatus; import com.wultra.app.enrollmentserver.model.integration.*; +import com.wultra.app.onboardingserver.api.errorhandling.DocumentVerificationException; +import com.wultra.app.onboardingserver.api.provider.DocumentVerificationProvider; import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.docverify.mock.MockConst; -import com.wultra.app.onboardingserver.api.errorhandling.DocumentVerificationException; -import com.wultra.app.onboardingserver.api.provider.DocumentVerificationProvider; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @@ -65,11 +65,12 @@ public class WultraMockDocumentVerificationProvider implements DocumentVerificat public WultraMockDocumentVerificationProvider() { logger.warn("Using mocked version of {}", DocumentVerificationProvider.class.getName()); - submittedDocs = CacheBuilder.newBuilder() + // TODO (racansky, 2024-01-04) consider removing Caffeine dependency and replace it by simple LinkedHashMap#removeEldestEntry + submittedDocs = Caffeine.newBuilder() .expireAfterWrite(Duration.ofHours(1)) .build(); - verificationUploadIds = CacheBuilder.newBuilder() + verificationUploadIds = Caffeine.newBuilder() .expireAfterWrite(Duration.ofHours(1)) .build(); } @@ -242,7 +243,7 @@ private DocumentSubmitResult toDocumentSubmitResult(final SubmittedDocument docu if (docId.startsWith("upload")) { uploadedDocId = docId; } else { - uploadedDocId = Ascii.truncate("uploaded-" + docId, 36, "..."); + uploadedDocId = StringUtils.truncate("uploaded-" + docId, 33) + "..."; } submitResult.setUploadId(uploadedDocId); submitResult.setValidationResult("{\"validationResult\": { \"data\": \"" + docId + "\" } }"); diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java index 640e1cde..cd4b19f2 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java @@ -21,7 +21,9 @@ import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin; import com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase; import com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus; +import com.wultra.app.enrollmentserver.model.integration.DocumentSubmitResult; import com.wultra.app.enrollmentserver.model.integration.OwnerId; +import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity; import com.wultra.app.onboardingserver.common.service.AuditService; @@ -33,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Set; import static com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus.ACCEPTED; @@ -86,20 +89,27 @@ public ClientEvaluationService( public void processClientEvaluation(final IdentityVerificationEntity identityVerification, final OwnerId ownerId) { logger.debug("Client evaluation started for {}", identityVerification); + final Set acceptedDocuments = selectAcceptedDocuments(identityVerification); + final String verificationId; try { - verificationId = getVerificationId(identityVerification); + verificationId = fetchVerificationId(identityVerification, acceptedDocuments); } catch (Exception e) { processVerificationIdError(identityVerification, ownerId, e); return; } - final EvaluateClientRequest request = EvaluateClientRequest.builder() + final EvaluateClientRequest.EvaluateClientRequestBuilder requestBuilder = EvaluateClientRequest.builder() .processId(identityVerification.getProcessId()) .userId(identityVerification.getUserId()) .identityVerificationId(identityVerification.getId()) .verificationId(verificationId) - .build(); + .provider(config.getDocumentVerificationProvider()); + + if (config.isSendingExtractedDataEnabled()) { + requestBuilder.extractedData(fetchDocumentsExtractedData(acceptedDocuments, identityVerification)); + } + final EvaluateClientRequest request = requestBuilder.build(); final int maxFailedAttempts = config.getClientEvaluationMaxFailedAttempts(); for (int i = 0; i < maxFailedAttempts; i++) { @@ -117,10 +127,29 @@ public void processClientEvaluation(final IdentityVerificationEntity identityVer processTooManyEvaluationError(identityVerification, ownerId); } - private static String getVerificationId(final IdentityVerificationEntity identityVerification) { - final Set verificationIds = identityVerification.getDocumentVerifications().stream() + private static Set selectAcceptedDocuments(final IdentityVerificationEntity identityVerification) { + return identityVerification.getDocumentVerifications().stream() .filter(DocumentVerificationEntity::isUsedForVerification) .filter(it -> it.getStatus() == DocumentStatus.ACCEPTED) + .collect(toSet()); + } + + private static List fetchDocumentsExtractedData(final Set documents, final IdentityVerificationEntity identityVerification) { + return documents.stream() + .map(doc -> selectLatestDocumentResult(doc, identityVerification)) + .map(DocumentResultEntity::getExtractedData) + .filter(data -> !DocumentSubmitResult.NO_DATA_EXTRACTED.equals(data)) + .toList(); + } + + private static DocumentResultEntity selectLatestDocumentResult(final DocumentVerificationEntity documentVerificationEntity, final IdentityVerificationEntity identityVerification) { + return documentVerificationEntity.getResults().stream() + .findFirst() + .orElseThrow(() -> new IllegalStateException("Missing document result for %s of %s".formatted(documentVerificationEntity, identityVerification))); + } + + private static String fetchVerificationId(final IdentityVerificationEntity identityVerification, final Set documents) { + final Set verificationIds = documents.stream() .map(DocumentVerificationEntity::getVerificationId) .collect(toSet()); 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 f4c8c2d8..907d9ad4 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 @@ -530,21 +530,9 @@ public Image getPhotoById(final String photoId, final OwnerId ownerId) throws Do } public List createDocsMetadata(List entities) { - List docsMetadata = new ArrayList<>(); - entities.forEach(entity -> { - DocumentMetadataResponseDto docMetadata = toDocumentMetadata(entity); - - if (DocumentStatus.REJECTED.equals(entity.getStatus())) { - List errors = collectRejectionErrors(entity); - if (docMetadata.getErrors() == null) { - docMetadata.setErrors(new ArrayList<>()); - } - docMetadata.getErrors().addAll(errors); - } - - docsMetadata.add(docMetadata); - }); - return docsMetadata; + return entities.stream() + .map(this::toDocumentMetadata) + .toList(); } /** @@ -616,8 +604,9 @@ private List collectRejectionErrors(DocumentVerificationEntity entity) { private DocumentMetadataResponseDto toDocumentMetadata(DocumentVerificationEntity entity) { DocumentMetadataResponseDto docMetadata = new DocumentMetadataResponseDto(); docMetadata.setId(entity.getId()); - if (StringUtils.isNotBlank(entity.getErrorDetail())) { - docMetadata.setErrors(List.of(entity.getErrorDetail())); + // Hide specific error reason if any. + if (StringUtils.isNotBlank(entity.getErrorDetail()) || StringUtils.isNotBlank(entity.getRejectReason())) { + docMetadata.setErrors(List.of("Error verifying the document.")); } docMetadata.setFilename(entity.getFilename()); docMetadata.setSide(entity.getSide()); diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ImageProcessor.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ImageProcessor.java index 2b4db63e..3dd5d7d6 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ImageProcessor.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ImageProcessor.java @@ -17,7 +17,6 @@ */ package com.wultra.app.onboardingserver.impl.service; -import com.google.common.io.Files; import com.wultra.app.enrollmentserver.model.integration.Image; import com.wultra.app.enrollmentserver.model.integration.OwnerId; import com.wultra.app.onboardingserver.api.errorhandling.PresenceCheckException; @@ -74,7 +73,7 @@ public Image upscaleImage(final OwnerId ownerId, final Image sourceImage, final final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(bufferedOutputImage, TYPE_PNG, outputStream); - final String filenamePng = Files.getNameWithoutExtension(filename) + SUFFIX_PNG; + final String filenamePng = getFilenameWithoutExtension(filename) + SUFFIX_PNG; final byte[] targetData = outputStream.toByteArray(); logger.debug("Image: {}, size: {} KB, {}", filenamePng, targetData.length / KILOBYTE, ownerId); @@ -90,4 +89,12 @@ public Image upscaleImage(final OwnerId ownerId, final Image sourceImage, final throw new PresenceCheckException("Unable to read image", e); } } + + private static String getFilenameWithoutExtension(final String filename) { + if (filename.contains(".")) { + return filename.substring(0, filename.lastIndexOf(".")); + } else { + return filename; + } + } } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckService.java index 12471f92..4c356a58 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckService.java @@ -17,8 +17,6 @@ */ package com.wultra.app.onboardingserver.impl.service; -import com.google.common.base.Ascii; -import com.google.common.base.Preconditions; import com.wultra.app.enrollmentserver.model.enumeration.*; import com.wultra.app.enrollmentserver.model.integration.*; import com.wultra.app.onboardingserver.api.errorhandling.DocumentVerificationException; @@ -40,6 +38,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -151,7 +150,7 @@ private void submitSelfiePhoto(final OwnerId ownerId, final IdentityVerification final SubmittedDocument submittedDoc = new SubmittedDocument(); // TODO use different random id approach submittedDoc.setDocumentId( - Ascii.truncate("selfie-photo-" + ownerId.getActivationId(), 36, "...") + StringUtils.truncate("selfie-photo-" + ownerId.getActivationId(), 33) + "..." ); submittedDoc.setPhoto(photo); submittedDoc.setType(DocumentType.SELFIE_PHOTO); @@ -290,7 +289,7 @@ private List getDocsWithPhoto(final IdentityVerifica } docsWithPhoto.forEach(docWithPhoto -> - Preconditions.checkNotNull(docWithPhoto.getPhotoId(), "Expected photoId value in " + docWithPhoto) + Validate.notNull(docWithPhoto.getPhotoId(), "Expected photoId value in " + docWithPhoto) ); return docsWithPhoto; diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/document/DocumentProcessingService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/document/DocumentProcessingService.java index 7dae9452..99a91b9d 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/document/DocumentProcessingService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/document/DocumentProcessingService.java @@ -17,7 +17,6 @@ */ package com.wultra.app.onboardingserver.impl.service.document; -import com.google.common.base.Strings; import com.wultra.app.enrollmentserver.api.model.onboarding.request.DocumentSubmitRequest; import com.wultra.app.enrollmentserver.model.Document; import com.wultra.app.enrollmentserver.model.DocumentMetadata; @@ -239,7 +238,7 @@ private void checkDocumentResubmit(final OwnerId ownerId, final DocumentSubmitRe * @param docVerification Resubmitted document. */ private void handleResubmit(final OwnerId ownerId, final String originalDocumentId, final DocumentVerificationEntity docVerification) { - if (Strings.isNullOrEmpty(originalDocumentId)) { + if (StringUtils.isBlank(originalDocumentId)) { logger.debug("Document {} is not a resubmit {}", docVerification, ownerId); return; } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/util/ConditionalOnPropertyNotEmpty.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/util/ConditionalOnPropertyNotEmpty.java index 69ee892f..bbd818c6 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/util/ConditionalOnPropertyNotEmpty.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/util/ConditionalOnPropertyNotEmpty.java @@ -18,7 +18,7 @@ package com.wultra.app.onboardingserver.impl.util; -import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -52,7 +52,7 @@ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) Map attrs = metadata.getAnnotationAttributes(ConditionalOnPropertyNotEmpty.class.getName()); String propertyName = (String) Objects.requireNonNull(attrs).get("value"); String val = context.getEnvironment().getProperty(propertyName); - return !Strings.nullToEmpty(val).trim().isEmpty(); + return StringUtils.isNotBlank(val); } } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java index b175d88d..3ee5c759 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java @@ -22,6 +22,8 @@ import com.wultra.core.annotations.PublicApi; import lombok.*; +import java.util.List; + /** * Request object for {@link OnboardingProvider#evaluateClient(EvaluateClientRequest)}. * @@ -45,4 +47,11 @@ public final class EvaluateClientRequest { @NonNull private String verificationId; + + private String provider; + + /** + * Data extracted from each document/page. Format is defined by the document verification provider used. + */ + private List extractedData; } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java index f636460d..8e73a915 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java @@ -19,6 +19,8 @@ import lombok.Data; +import java.util.List; + /** * Request object for client evaluation. * @@ -39,4 +41,9 @@ class ClientEvaluateRequestDto { private String verificationId; private String provider; + + /** + * Data extracted from each document/page. Format is defined by the document verification provider used. + */ + private List extractedData; } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java index 814ebd87..d6607bed 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java @@ -245,6 +245,8 @@ private static ClientEvaluateRequestDto convert(final EvaluateClientRequest sour target.setIdentityVerificationId(source.getIdentityVerificationId()); target.setUserId(source.getUserId()); target.setVerificationId(source.getVerificationId()); + target.setProvider(source.getProvider()); + target.setExtractedData(source.getExtractedData()); return target; } } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/util/StateContextUtil.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/util/StateContextUtil.java index 8b45bac1..085db8d4 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/util/StateContextUtil.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/util/StateContextUtil.java @@ -16,11 +16,11 @@ */ package com.wultra.app.onboardingserver.statemachine.util; -import com.google.common.base.Preconditions; import com.wultra.app.onboardingserver.statemachine.consts.ExtendedStateVariable; import com.wultra.app.onboardingserver.statemachine.enums.OnboardingEvent; import com.wultra.app.onboardingserver.statemachine.enums.OnboardingState; import io.getlime.core.rest.model.base.response.Response; +import org.apache.commons.lang3.Validate; import org.springframework.http.HttpStatus; import org.springframework.statemachine.StateContext; @@ -38,7 +38,7 @@ private StateContextUtil() { } public static void setResponseOk(final StateContext context, final Response response) { - Preconditions.checkArgument( + Validate.isTrue( !context.getStateMachine().hasStateMachineError(), String.format("Found state machine error in %s, when expected ok", context) ); diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/task/cleaning/CleaningService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/task/cleaning/CleaningService.java index 7d32cdcd..cbf91a4f 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/task/cleaning/CleaningService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/task/cleaning/CleaningService.java @@ -17,7 +17,6 @@ */ package com.wultra.app.onboardingserver.task.cleaning; -import com.google.common.collect.Lists; import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus; import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin; import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus; @@ -33,8 +32,11 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Duration; +import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Service with cleaning functionality. @@ -120,7 +122,7 @@ public void terminateExpiredOtpCodes() { final Date createdDateExpiredOtp = DateUtil.convertExpirationToCreatedDate(otpExpiration); final List otpIds = onboardingOtpRepository.findExpiredIds(createdDateExpiredOtp); final Date now = new Date(); - for (List otpIdChunk : Lists.partition(otpIds, BATCH_SIZE)) { + for (List otpIdChunk : ListUtils.partition(otpIds, BATCH_SIZE)) { terminateAndAuditOtps(otpIdChunk, now); } } @@ -139,7 +141,7 @@ public void terminateExpiredProcesses() { return; } logger.info("Terminating {} expired processes", ids.size()); - for (List idsChunk : Lists.partition(ids, BATCH_SIZE)) { + for (List idsChunk : ListUtils.partition(ids, BATCH_SIZE)) { terminateAndAuditProcesses(idsChunk, now, OnboardingProcessEntity.ERROR_PROCESS_EXPIRED_ONBOARDING, ErrorOrigin.PROCESS_LIMIT_CHECK); } } @@ -165,7 +167,7 @@ public void terminateExpiredDocumentVerifications() { } final Date now = new Date(); - for (List idsChunk : Lists.partition(ids, BATCH_SIZE)) { + for (List idsChunk : ListUtils.partition(ids, BATCH_SIZE)) { logger.info("Terminating {} expired document verifications", idsChunk.size()); terminateAndAuditDocuments(idsChunk, now, ERROR_MESSAGE_DOCUMENT_VERIFICATION_EXPIRED, ErrorOrigin.PROCESS_LIMIT_CHECK); } @@ -184,7 +186,7 @@ public void terminateExpiredIdentityVerifications() { final Date now = new Date(); final ErrorOrigin errorOrigin = ErrorOrigin.PROCESS_LIMIT_CHECK; - for (List idsChunk : Lists.partition(ids, BATCH_SIZE)) { + for (List idsChunk : ListUtils.partition(ids, BATCH_SIZE)) { logger.info("Terminating {} expired identity verifications", idsChunk.size()); terminateAndAuditIdentityVerifications(idsChunk, now, OnboardingProcessEntity.ERROR_PROCESS_EXPIRED_ONBOARDING, errorOrigin); } @@ -207,7 +209,7 @@ private void terminateProcessesAndRelatedEntities(final List processIds, final Date now = new Date(); final ErrorOrigin errorOrigin = ErrorOrigin.PROCESS_LIMIT_CHECK; - for (List processIdChunk : Lists.partition(processIds, BATCH_SIZE)) { + for (List processIdChunk : ListUtils.partition(processIds, BATCH_SIZE)) { logger.info("Terminating {} processes", processIdChunk.size()); terminateAndAuditProcesses(processIdChunk, now, errorDetail, errorOrigin); @@ -248,4 +250,21 @@ private void terminateAndAuditDocuments(final List documentIds, final Da documentVerificationRepository.findById(documentId).ifPresent(document -> auditService.audit(document, "Expired Document verification for user: {}, {}", document.getIdentityVerification().getUserId(), errorDetail))); } + + protected static final class ListUtils { + + private ListUtils() { + throw new IllegalStateException("Utility class"); + } + + public static Collection> partition(final List source, final int partitionSize) { + if (source.size() <= partitionSize) { + return List.of(source); + } + return IntStream.range(0, source.size()) + .boxed() + .collect(Collectors.groupingBy(partition -> (partition / partitionSize), Collectors.mapping(source::get, Collectors.toList()))) + .values(); + } + } } diff --git a/enrollment-server-onboarding/src/main/resources/application.properties b/enrollment-server-onboarding/src/main/resources/application.properties index 77e71e94..fe3aa094 100644 --- a/enrollment-server-onboarding/src/main/resources/application.properties +++ b/enrollment-server-onboarding/src/main/resources/application.properties @@ -28,7 +28,6 @@ banner.application.version=@project.version@ spring.datasource.url=jdbc:postgresql://localhost:5432/powerauth spring.datasource.username=powerauth spring.datasource.password= -spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.hikari.auto-commit=false spring.jpa.properties.hibernate.connection.characterEncoding=utf8 spring.jpa.properties.hibernate.connection.useUnicode=true @@ -37,7 +36,6 @@ spring.jpa.properties.hibernate.connection.useUnicode=true #spring.datasource.url=jdbc:oracle:thin:@//127.0.0.1:1521/powerauth #spring.datasource.username=powerauth #spring.datasource.password= -#spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver # Hibernate Configuration spring.jpa.hibernate.ddl-auto=none @@ -90,6 +88,7 @@ enrollment-server-onboarding.onboarding-process.max-error-score=15 # Client Evaluation Configuration enrollment-server-onboarding.client-evaluation.max-failed-attempts=5 +enrollment-server-onboarding.client-evaluation.include-extracted-data=false # Identity Verification Configuration enrollment-server-onboarding.identity-verification.enabled=false @@ -222,6 +221,7 @@ powerauth.service.correlation-header.value.validation-regexp=[a-zA-Z0-9\\-]{8,10 #logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) [%X{X-Correlation-ID}] %clr(%5p) %clr(${PID: }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx} # Monitoring +management.tracing.sampling.probability=1.0 #management.endpoint.metrics.enabled=true #management.endpoints.web.exposure.include=health, prometheus #management.endpoint.prometheus.enabled=true diff --git a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java index 28caa5d0..55dd8ec5 100644 --- a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java +++ b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java @@ -19,7 +19,9 @@ import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus; import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin; +import com.wultra.app.enrollmentserver.model.integration.DocumentSubmitResult; import com.wultra.app.enrollmentserver.model.integration.OwnerId; +import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity; import com.wultra.app.onboardingserver.common.service.AuditService; @@ -34,6 +36,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -71,12 +74,15 @@ class ClientEvaluationServiceTest { void testProcessClientEvaluation_successful() throws Exception { when(identityVerificationConfig.getClientEvaluationMaxFailedAttempts()) .thenReturn(1); + when(identityVerificationConfig.isSendingExtractedDataEnabled()) + .thenReturn(true); final EvaluateClientRequest evaluateClientRequest = EvaluateClientRequest.builder() .processId("p1") .userId("u1") .identityVerificationId("i1") .verificationId("v1") + .extractedData(List.of("d1_data")) .build(); final EvaluateClientResponse evaluateClientResponse = EvaluateClientResponse.builder() .accepted(true) @@ -90,8 +96,8 @@ void testProcessClientEvaluation_successful() throws Exception { identityVerification.setUserId("u1"); identityVerification.setPhase(CLIENT_EVALUATION); identityVerification.setDocumentVerifications(Set.of( - createDocumentVerification("d1", DocumentStatus.ACCEPTED, "v1"), - createDocumentVerification("d2", DocumentStatus.ACCEPTED, "v1"), + createDocumentVerificationWithResults("d1", DocumentStatus.ACCEPTED, "v1", "d1_data"), + createDocumentVerificationWithResults("d2", DocumentStatus.ACCEPTED, "v1", DocumentSubmitResult.NO_DATA_EXTRACTED), createDocumentVerification("d3", DocumentStatus.DISPOSED, "v2"))); final OwnerId ownerId = new OwnerId(); @@ -166,4 +172,13 @@ private static DocumentVerificationEntity createDocumentVerification(final Strin documentVerification.setUsedForVerification(true); return documentVerification; } + + private static DocumentVerificationEntity createDocumentVerificationWithResults(final String id, final DocumentStatus status, final String verificationId, final String extractedData) { + final DocumentResultEntity documentResult = new DocumentResultEntity(); + documentResult.setExtractedData(extractedData); + + final DocumentVerificationEntity documentVerification = createDocumentVerification(id, status, verificationId); + documentVerification.setResults(Set.of(documentResult)); + return documentVerification; + } } diff --git a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationServiceTest.java b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationServiceTest.java index 02fda8c0..a9fb95c7 100644 --- a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationServiceTest.java +++ b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationServiceTest.java @@ -17,9 +17,13 @@ */ package com.wultra.app.onboardingserver.impl.service; +import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus; import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin; import com.wultra.app.enrollmentserver.model.integration.OwnerId; +import com.wultra.app.onboardingserver.api.provider.DocumentVerificationProvider; import com.wultra.app.onboardingserver.common.database.IdentityVerificationRepository; +import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; +import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity; import com.wultra.app.onboardingserver.common.service.AuditService; import org.junit.jupiter.api.Test; @@ -28,6 +32,10 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Set; import static com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase.COMPLETED; import static com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase.OTP_VERIFICATION; @@ -35,6 +43,7 @@ import static com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus.FAILED; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; /** @@ -54,6 +63,9 @@ class IdentityVerificationServiceTest { @Mock private IdentityVerificationPrecompleteCheck identityVerificationPrecompleteCheck; + @Mock + private DocumentVerificationProvider documentVerificationProvider; + @InjectMocks private IdentityVerificationService tested; @@ -96,4 +108,49 @@ void testProcessDocumentVerificationResult_invalidPrecompleteGuard() throws Exce assertThat(savedIdentityVerification.getErrorDetail(), equalTo("documentVerificationFailed")); assertThat(savedIdentityVerification.getErrorOrigin(), equalTo(ErrorOrigin.FINAL_VALIDATION)); } + + @Test + void testCreateDocsMetadata_hideRejectedErrorDetail() { + final DocumentVerificationEntity doc = new DocumentVerificationEntity(); + doc.setStatus(DocumentStatus.REJECTED); + doc.setErrorDetail("Hide specific error occurred."); + + final List errors = tested.createDocsMetadata(List.of(doc)).get(0).getErrors(); + assertHidden(errors); + } + + @Test + void testCreateDocsMetadata_hideRejectedRejectReason() { + final DocumentVerificationEntity doc = new DocumentVerificationEntity(); + doc.setStatus(DocumentStatus.REJECTED); + doc.setRejectReason("Hide specific rejection reason."); + + final List errors = tested.createDocsMetadata(List.of(doc)).get(0).getErrors(); + assertHidden(errors); + } + + @Test + void testCreateDocsMetadata_hideFailedErrorDetail() { + final DocumentVerificationEntity doc = new DocumentVerificationEntity(); + doc.setStatus(DocumentStatus.FAILED); + doc.setErrorDetail("Hide some error occurred."); + + final List errors = tested.createDocsMetadata(List.of(doc)).get(0).getErrors(); + assertHidden(errors); + } + + @Test + void testCreateDocsMetadata_accepted() { + final DocumentVerificationEntity doc = new DocumentVerificationEntity(); + doc.setStatus(DocumentStatus.ACCEPTED); + + final List errors = tested.createDocsMetadata(List.of(doc)).get(0).getErrors(); + assertTrue(CollectionUtils.isEmpty(errors)); + } + + private static void assertHidden(final List errors) { + assertEquals(1, errors.size()); + assertEquals("Error verifying the document.", errors.get(0)); + } + } diff --git a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/task/cleaning/CleaningServiceTest.java b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/task/cleaning/CleaningServiceTest.java index 12ccf3fa..8022720c 100644 --- a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/task/cleaning/CleaningServiceTest.java +++ b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/task/cleaning/CleaningServiceTest.java @@ -28,6 +28,11 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + import static com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin.PROCESS_LIMIT_CHECK; import static org.junit.jupiter.api.Assertions.*; @@ -227,6 +232,30 @@ void testTerminateExpiredProcessActivations() { assertEquals(PROCESS_LIMIT_CHECK, documentVerification.getErrorOrigin()); } + @Test + void testPartition() { + final List source = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h"); + + final Collection> result = CleaningService.ListUtils.partition(source, 3); + + assertEquals(3, result.size()); + + final Iterator> iterator = result.iterator(); + assertEquals(List.of("a", "b", "c"), iterator.next()); + assertEquals(List.of("d", "e", "f"), iterator.next()); + assertEquals(List.of("g", "h"), iterator.next()); + } + + @Test + void testPartition_tooSmall() { + final List source = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h"); + + final Collection> result = CleaningService.ListUtils.partition(source, 10); + + assertEquals(1, result.size()); + assertEquals(List.of("a", "b", "c", "d", "e", "f", "g", "h"), result.iterator().next()); + } + private void assertStatus(final String id, final DocumentStatus status) { final DocumentVerificationEntity documentVerification = fetchDocumentVerification(id); assertEquals(status, documentVerification.getStatus(), "status of " + id); diff --git a/enrollment-server-onboarding/src/test/resources/application-test.properties b/enrollment-server-onboarding/src/test/resources/application-test.properties index 5dff6a04..5ee3baba 100644 --- a/enrollment-server-onboarding/src/test/resources/application-test.properties +++ b/enrollment-server-onboarding/src/test/resources/application-test.properties @@ -18,7 +18,6 @@ spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE spring.datasource.username=sa spring.datasource.password=password -spring.datasource.driver-class-name=org.h2.Driver spring.jpa.hibernate.ddl-auto=create spring.liquibase.enabled=false diff --git a/enrollment-server/pom.xml b/enrollment-server/pom.xml index 995cfb9f..f438a954 100644 --- a/enrollment-server/pom.xml +++ b/enrollment-server/pom.xml @@ -30,7 +30,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT @@ -128,6 +128,16 @@ micrometer-registry-prometheus + + io.projectreactor + reactor-core-micrometer + + + + io.micrometer + micrometer-tracing-bridge-otel + + org.springframework.boot diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java index ecaf31ce..7b9705e9 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java @@ -72,17 +72,11 @@ public Response registerDevice( throw new InvalidRequestObjectException(); } - // Get the values from the request - final String platform = requestObject.getPlatform(); + final MobilePlatform platform = convert(requestObject.getPlatform()); final String token = requestObject.getToken(); - // Register the device and return response - MobilePlatform mobilePlatform = MobilePlatform.Android; - if ("ios".equalsIgnoreCase(platform)) { - mobilePlatform = MobilePlatform.iOS; - } try { - final boolean result = client.createDevice(applicationId, token, mobilePlatform, activationId); + final boolean result = client.createDevice(applicationId, token, platform, activationId); if (result) { logger.info("Push registration succeeded, user ID: {}", userId); return new Response(); @@ -96,4 +90,11 @@ public Response registerDevice( } } + private static MobilePlatform convert(final PushRegisterRequest.Platform source) { + return switch (source) { + case IOS -> MobilePlatform.IOS; + case ANDROID -> MobilePlatform.ANDROID; + }; + } + } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/util/ConditionalOnPropertyNotEmpty.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/util/ConditionalOnPropertyNotEmpty.java index 89a6e877..e96aa8c4 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/util/ConditionalOnPropertyNotEmpty.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/util/ConditionalOnPropertyNotEmpty.java @@ -18,7 +18,7 @@ package com.wultra.app.enrollmentserver.impl.util; -import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -52,7 +52,7 @@ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) Map attrs = metadata.getAnnotationAttributes(ConditionalOnPropertyNotEmpty.class.getName()); String propertyName = (String) Objects.requireNonNull(attrs).get("value"); String val = context.getEnvironment().getProperty(propertyName); - return !Strings.nullToEmpty(val).trim().isEmpty(); + return StringUtils.isNotBlank(val); } } diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/PushRegisterRequestValidator.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/PushRegisterRequestValidator.java index aa0fe01c..e0a731f9 100644 --- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/PushRegisterRequestValidator.java +++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/PushRegisterRequestValidator.java @@ -42,11 +42,8 @@ public static String validate(PushRegisterRequest request) { } // Validate mobile platform - final String platform = request.getPlatform(); - if (StringUtils.isBlank(platform)) { + if (request.getPlatform() == null) { return "No mobile platform was provided when registering for push messages."; - } else if (!"ios".equalsIgnoreCase(platform) && !"android".equalsIgnoreCase(platform)) { // must be iOS or Android - return "Unknown mobile platform was provided when registering for push messages."; } // Validate push token @@ -56,7 +53,6 @@ public static String validate(PushRegisterRequest request) { } return null; - } } diff --git a/enrollment-server/src/main/resources/application.properties b/enrollment-server/src/main/resources/application.properties index 12e3db53..a15ca8e1 100644 --- a/enrollment-server/src/main/resources/application.properties +++ b/enrollment-server/src/main/resources/application.properties @@ -28,7 +28,6 @@ banner.application.version=@project.version@ spring.datasource.url=jdbc:postgresql://localhost:5432/powerauth spring.datasource.username=powerauth spring.datasource.password= -spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.hikari.auto-commit=false spring.jpa.properties.hibernate.connection.characterEncoding=utf8 spring.jpa.properties.hibernate.connection.useUnicode=true @@ -37,7 +36,6 @@ spring.jpa.properties.hibernate.connection.useUnicode=true #spring.datasource.url=jdbc:oracle:thin:@//127.0.0.1:1521/powerauth #spring.datasource.username=powerauth #spring.datasource.password= -#spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver # Hibernate Configuration spring.jpa.hibernate.ddl-auto=none @@ -90,7 +88,10 @@ powerauth.service.correlation-header.value.validation-regexp=[a-zA-Z0-9\\-]{8,10 #logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) [%X{X-Correlation-ID}] %clr(%5p) %clr(${PID: }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx} # Monitoring +management.tracing.sampling.probability=1.0 #management.endpoint.metrics.enabled=true #management.endpoints.web.exposure.include=health, prometheus #management.endpoint.prometheus.enabled=true #management.prometheus.metrics.export.enabled=true + +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration diff --git a/enrollment-server/src/test/java/com/wultra/app/enrollmentserver/impl/service/converter/MobileTokenConverterTest.java b/enrollment-server/src/test/java/com/wultra/app/enrollmentserver/impl/service/converter/MobileTokenConverterTest.java index c17ce661..16e32c57 100644 --- a/enrollment-server/src/test/java/com/wultra/app/enrollmentserver/impl/service/converter/MobileTokenConverterTest.java +++ b/enrollment-server/src/test/java/com/wultra/app/enrollmentserver/impl/service/converter/MobileTokenConverterTest.java @@ -18,7 +18,6 @@ package com.wultra.app.enrollmentserver.impl.service.converter; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableMap; import com.wultra.app.enrollmentserver.database.entity.OperationTemplateEntity; import com.wultra.app.enrollmentserver.errorhandling.MobileTokenConfigurationException; import com.wultra.security.powerauth.client.model.enumeration.OperationStatus; @@ -362,27 +361,27 @@ void testConvertUiPostApprovalGenericMessageWithSubstitutedDangerousChars() thro @Test void testConvertAttributes() throws Exception { final OperationDetailResponse operationDetail = createOperationDetailResponse(); - operationDetail.setParameters(ImmutableMap.builder() - .put("amount", "13.7") - .put("currency", "EUR") - .put("iban", "AT483200000012345864") - .put("note", "Remember me") - .put("headingLevel", "3") - .put("thumbnailUrl", "https://example.com/123_thumb.jpeg") - .put("originalUrl", "https://example.com/123.jpeg") - .put("sourceAmount", "1.26") - .put("sourceCurrency", "ETH") - .put("targetAmount", "1710.98") - .put("targetCurrency", "USD") - .put("dynamic", "true") - .put("partyLogoUrl", "https://example.com/img/logo/logo.svg") - .put("partyName", "Example Ltd.") - .put("partyDescription", "Find out more about Example...") - .put("partyUrl", "https://example.com/hello") - .put("alertType", "WARNING") - .put("alertTitle", "Insufficient Balance") - .put("alertMessage", "You have only $1.00 on your account with number 238400856/0300.") - .build()); + operationDetail.setParameters(Map.ofEntries( + Map.entry("amount", "13.7"), + Map.entry("currency", "EUR"), + Map.entry("iban", "AT483200000012345864"), + Map.entry("note", "Remember me"), + Map.entry("headingLevel", "3"), + Map.entry("thumbnailUrl", "https://example.com/123_thumb.jpeg"), + Map.entry("originalUrl", "https://example.com/123.jpeg"), + Map.entry("sourceAmount", "1.26"), + Map.entry("sourceCurrency", "ETH"), + Map.entry("targetAmount", "1710.98"), + Map.entry("targetCurrency", "USD"), + Map.entry("dynamic", "true"), + Map.entry("partyLogoUrl", "https://example.com/img/logo/logo.svg"), + Map.entry("partyName", "Example Ltd."), + Map.entry("partyDescription", "Find out more about Example..."), + Map.entry("partyUrl", "https://example.com/hello"), + Map.entry("alertType", "WARNING"), + Map.entry("alertTitle", "Insufficient Balance"), + Map.entry("alertMessage", "You have only $1.00 on your account with number 238400856/0300.") + )); final OperationTemplateEntity operationTemplate = new OperationTemplateEntity(); operationTemplate.setAttributes(""" @@ -518,10 +517,10 @@ void testConvertAttributes() throws Exception { @Test void testConvertAmount_notANumber() throws Exception { final OperationDetailResponse operationDetail = createOperationDetailResponse(); - operationDetail.setParameters(ImmutableMap.builder() - .put("amount", "not a number") - .put("currency", "CZK") - .build()); + operationDetail.setParameters(Map.of( + "amount", "not a number", + "currency", "CZK" + )); final OperationTemplateEntity operationTemplate = new OperationTemplateEntity(); operationTemplate.setAttributes(""" @@ -558,13 +557,13 @@ void testConvertAmount_notANumber() throws Exception { @Test void testConvertAmountConversion_sourceNotANumber() throws Exception { final OperationDetailResponse operationDetail = createOperationDetailResponse(); - operationDetail.setParameters(ImmutableMap.builder() - .put("sourceAmount", "source not a number") - .put("sourceCurrency", "EUR") - .put("targetAmount", "1710.98") - .put("targetCurrency", "USD") - .put("dynamic", "true") - .build()); + operationDetail.setParameters(Map.of( + "sourceAmount", "source not a number", + "sourceCurrency", "EUR", + "targetAmount", "1710.98", + "targetCurrency", "USD", + "dynamic", "true" + )); final OperationTemplateEntity operationTemplate = new OperationTemplateEntity(); operationTemplate.setAttributes(""" @@ -610,13 +609,13 @@ void testConvertAmountConversion_sourceNotANumber() throws Exception { @Test void testConvertAmountConversion_targetNotANumber() throws Exception { final OperationDetailResponse operationDetail = createOperationDetailResponse(); - operationDetail.setParameters(ImmutableMap.builder() - .put("sourceAmount", "1710.98") - .put("sourceCurrency", "USD") - .put("targetAmount", "target not a number") - .put("targetCurrency", "EUR") - .put("dynamic", "true") - .build()); + operationDetail.setParameters(Map.of( + "sourceAmount", "1710.98", + "sourceCurrency", "USD", + "targetAmount", "target not a number", + "targetCurrency", "EUR", + "dynamic", "true" + )); final OperationTemplateEntity operationTemplate = new OperationTemplateEntity(); operationTemplate.setAttributes(""" diff --git a/mtoken-model/pom.xml b/mtoken-model/pom.xml index a95dd784..369b2419 100644 --- a/mtoken-model/pom.xml +++ b/mtoken-model/pom.xml @@ -26,7 +26,7 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index a5f9bbd4..606baa41 100644 --- a/pom.xml +++ b/pom.xml @@ -26,13 +26,13 @@ com.wultra.security enrollment-server-parent - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT pom org.springframework.boot spring-boot-starter-parent - 3.1.6 + 3.2.2 @@ -94,20 +94,15 @@ 4.0.0 2.2.20 2.3.0 - 1.4.2 + 1.4.4 - - 3.13.0 - - 1.8.0 - 1.6.0 - 1.6.0 - 1.6.0 + 1.9.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT + 1.7.0-SNAPSHOT 1.77 7.4 - - 1.4.14 @@ -316,13 +311,6 @@ org.apache.maven.plugins maven-enforcer-plugin - - - de.skuzzle.enforcer - restrict-imports-enforcer-rule - 2.4.0 - - enforce-banned-dependencies @@ -336,26 +324,12 @@ org.apache.tomcat.embed:*:*:*:compile org.bouncycastle:bcpkix-jdk15on:*:*:compile org.bouncycastle:bcprov-jdk15on:*:*:compile + com.google.guava:guava*:*:*:compile - - enforce-banned-java-imports - - enforce - - - - - - Guava depends on jsr305 but we prefer jakarta in our code - javax.annotation.** - - - -