diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9f62dc964..ae5e8ae7a 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/maven-deploy.yml b/.github/workflows/maven-deploy.yml
index 9a1690fd0..35a0646df 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 6bdada9fe..33932bf61 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/Dockerfile b/Dockerfile
index 4f51ea7d4..e2a03d33a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM ibm-semeru-runtimes:open-17.0.9_9-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
diff --git a/deploy/enrollment-server.xml b/deploy/enrollment-server.xml
index c9a24edc2..8a20f2b6d 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 32128f21a..8c4ed3da2 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 46718a263..47b9b4559 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 |
@@ -30,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
@@ -63,6 +68,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/Migration-Instructions.md b/docs/Migration-Instructions.md
index 0bf91d4af..740c7d30d 100644
--- a/docs/Migration-Instructions.md
+++ b/docs/Migration-Instructions.md
@@ -2,6 +2,7 @@
This page contains PowerAuth Enrollment Server migration instructions.
+- [PowerAuth Enrollment Server 1.7.0](./PowerAuth-Enrollment-Server-1.7.0.md)
- [PowerAuth Enrollment Server 1.6.0](./PowerAuth-Enrollment-Server-1.6.0.md)
- [PowerAuth Enrollment Server 1.5.0](./PowerAuth-Enrollment-Server-1.5.0.md)
- [PowerAuth Enrollment Server 1.4.0](./PowerAuth-Enrollment-Server-1.4.0.md)
diff --git a/docs/PowerAuth-Enrollment-Server-1.7.0.md b/docs/PowerAuth-Enrollment-Server-1.7.0.md
new file mode 100644
index 000000000..f6b607ef8
--- /dev/null
+++ b/docs/PowerAuth-Enrollment-Server-1.7.0.md
@@ -0,0 +1,12 @@
+# Migration from 1.6.x to 1.7.x
+
+This guide contains instructions for migration from PowerAuth Enrollment Server version `1.6.x` to version `1.7.0`.
+
+
+## REST API
+
+
+### Register for Push Messages (Token)
+
+The endpoint `POST /api/push/device/register/token` now strictly validates `platform` against values `ios`, `android` or `huawei`.
+If you use the PowerAuth SDK, you should not be affected.
diff --git a/docs/onboarding/Configuration-Properties.md b/docs/onboarding/Configuration-Properties.md
index 799d45a15..3f1881e69 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 ed358b8bd..09d045190 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 d4fde669a..18c368b40 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
+ 1.7.0
@@ -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 21dee883a..11121cbe3 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,44 @@
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, FCM, or HMS services without any modification.
+ */
+ @NotBlank
+ @ToString.Exclude
+ @Schema(description = "The push token is the value received from APNS, FCM, or HMS services without any modification.")
private String token;
+ public enum Platform {
+ @JsonProperty("ios")
+ IOS,
+
+ @JsonProperty("android")
+ ANDROID,
+
+ @JsonProperty("huawei")
+ HUAWEI
+ }
+
}
diff --git a/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateListResponse.java b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateListResponse.java
new file mode 100644
index 000000000..248f0e423
--- /dev/null
+++ b/enrollment-server-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/enrollment/response/TemplateListResponse.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Template list response.
+ *
+ * @author Lubos Racansky, lubos.racansky@wultra.com
+ */
+@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-onboarding-adapter-mock/pom.xml b/enrollment-server-onboarding-adapter-mock/pom.xml
index e3216d78d..1ed13fcba 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
+ 1.7.0
enrollment-server-onboarding-adapter-mock
diff --git a/enrollment-server-onboarding-api-model/pom.xml b/enrollment-server-onboarding-api-model/pom.xml
index aea8d0ba1..3f532b6bb 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
+ 1.7.0
enrollment-server-onboarding-api-model
diff --git a/enrollment-server-onboarding-api/pom.xml b/enrollment-server-onboarding-api/pom.xml
index 82203d276..05397e10b 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
+ 1.7.0
com.wultra.security
diff --git a/enrollment-server-onboarding-common/pom.xml b/enrollment-server-onboarding-common/pom.xml
index 7c54ec298..99f9e0cf0 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
+ 1.7.0
enrollment-server-onboarding-common
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 51b51a942..a1e19ce6e 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-common/src/test/resources/application-test.properties b/enrollment-server-onboarding-common/src/test/resources/application-test.properties
index 308c2b3ac..98bb47ca1 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 cc0fb0b21..90dac5e39 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
+ 1.7.0
@@ -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 dc5df8145..3992bfef5 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 000000000..b763f6511
--- /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 1fd6417a4..ac6c40290 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
+ 1.7.0
com.wultra.security
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 5f660561e..d2f6a81db 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 8b97aa52e..cdf5061cc 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 007c06c6a..b4dca0c5a 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 505cfa31c..4cf145984 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
+ 1.7.0
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 d34579224..2521216b9 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 e6b8b938a..4cfed4951 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
+ 1.7.0
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 012d48715..1d5838598 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 c98964a2e..200cf86f8 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 e3f495278..4ca7e8b20 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
+ 1.7.0
@@ -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
@@ -185,10 +200,6 @@
-
- org.apache.maven.plugins
- maven-war-plugin
-
org.springframework.boot
spring-boot-maven-plugin
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 37e1d40be..379db3cd0 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 692830985..b4b384d69 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/errorhandling/DefaultExceptionHandler.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/errorhandling/DefaultExceptionHandler.java
index 94b07ebfd..ae2667f93 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-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 640e1cde8..cd4b19f2c 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 f4c8c2d8e..838ef2ad2 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();
}
/**
@@ -571,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) {
@@ -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 2b4db63ed..3dd5d7d67 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 12471f923..4c356a581 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 7dae9452b..99a91b9dd 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 69ee892fe..bbd818c6e 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 b175d88de..3ee5c7599 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 f636460dc..8e73a915f 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 814ebd876..d6607bed8 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/service/StateMachineService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/statemachine/service/StateMachineService.java
index 5b1357537..56a316f50 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());
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 8b45bac1e..085db8d41 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 7d32cdcd1..cbf91a4f0 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 09f3c82f0..e937a6aec 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
@@ -209,6 +208,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 28caa5d02..55dd8ec5a 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 02fda8c0c..a9fb95c73 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 12ccf3faf..8022720c0 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 5dff6a04e..5ee3babae 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 bea22f846..b49671061 100644
--- a/enrollment-server/pom.xml
+++ b/enrollment-server/pom.xml
@@ -30,7 +30,7 @@
com.wultra.security
enrollment-server-parent
- 1.6.0
+ 1.7.0
@@ -98,6 +98,10 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
jakarta.servlet
@@ -123,6 +127,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/configuration/OpenApiConfiguration.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/configuration/OpenApiConfiguration.java
index 69c69a1aa..0a5622140 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 51cdc35f8..2b92f3d2d 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/MobileTokenController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/MobileTokenController.java
index 1ef71aeb8..61927ad47 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/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 000000000..b1a9e8839
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/admin/AdminController.java
@@ -0,0 +1,89 @@
+/*
+ * 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.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.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;
+
+ private final ObjectMapper objectMapper;
+
+ @GetMapping("/template")
+ public ObjectResponse templates() {
+ logger.debug("Returning template list.");
+ final TemplateListResponse response = new TemplateListResponse();
+ response.addAll(convert(operationTemplateService.findAll()));
+ return new ObjectResponse<>(response);
+ }
+
+ private List convert(final List source) {
+ return source.stream()
+ .map(this::convert)
+ .toList();
+ }
+
+ private TemplateListResponse.TemplateDetail convert(final OperationTemplateEntity source) {
+ return TemplateListResponse.TemplateDetail.builder()
+ .name(source.getPlaceholder())
+ .title(source.getTitle())
+ .message(source.getMessage())
+ .language(source.getLanguage())
+ .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();
+ }
+ }
+}
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 40c605567..24ffa6306 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/errorhandling/DefaultExceptionHandler.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java
index 80ac42a4c..e85b95220 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.
@@ -49,6 +50,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.
@@ -145,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.");
+ }
}
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 000000000..34528c1de
--- /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/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 9aafce226..d3a93778e 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/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java
index ecaf31ceb..2ba048a4a 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,12 @@ public Response registerDevice(
}
}
+ private static MobilePlatform convert(final PushRegisterRequest.Platform source) {
+ return switch (source) {
+ case IOS -> MobilePlatform.IOS;
+ case ANDROID -> MobilePlatform.ANDROID;
+ case HUAWEI -> MobilePlatform.HUAWEI;
+ };
+ }
+
}
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 89a6e8771..e96aa8c48 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 aa0fe01ce..e0a731f9d 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-dev.properties b/enrollment-server/src/main/resources/application-dev.properties
index 524c75a0d..165c749e1 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 a0da944e3..195eaa2bc 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
@@ -70,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=
@@ -90,7 +98,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 c17ce6611..16e32c579 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 880febd13..d895f43aa 100644
--- a/mtoken-model/pom.xml
+++ b/mtoken-model/pom.xml
@@ -26,7 +26,7 @@
com.wultra.security
enrollment-server-parent
- 1.6.0
+ 1.7.0
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 a6ef3225a..787620eaa 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.
*/
diff --git a/pom.xml b/pom.xml
index 43206742c..e06f56583 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,13 +26,13 @@
com.wultra.security
enrollment-server-parent
- 1.6.0
+ 1.7.0
pom
org.springframework.boot
spring-boot-starter-parent
- 3.1.6
+ 3.2.4
@@ -88,26 +88,21 @@
- 7.2.0
+ 7.4.0
- 5.10.2
+ 5.13.0
4.0.0
- 2.2.20
- 2.3.0
- 1.4.2
+ 2.2.21
+ 2.5.0
+ 1.4.4
-
- 3.13.0
-
- 1.8.0
- 1.6.0
- 1.6.0
- 1.6.0
+ 1.9.0
+ 1.7.0
+ 1.7.0
+ 1.7.0
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.**
-
-
-
-