diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index d21fc919..e160a01a 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,13 +1,9 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-#
name: "CodeQL"
on:
+ workflow_dispatch:
+ branches:
+ - 'develop'
push:
branches: [ develop, main ]
pull_request:
@@ -17,63 +13,12 @@ on:
- cron: '0 2 * * 4'
jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: [ '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
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- - name: Set up JDK 17
- uses: actions/setup-java@v3
- with:
- java-version: 17
- distribution: 'temurin'
- cache: maven
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
-
- # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
- # queries: security-extended,security-and-quality
-
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
-
- # âšī¸ Command-line programs to run using the OS shell.
- # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-
- # If the Autobuild fails above, remove it and uncomment the following three lines.
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
-
- # - run: |
- # echo "Run, Build Application using script"
- # ./location_of_script_within_repo/buildscript.sh
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
- with:
- category: "/language:${{matrix.language}}"
+ codeql-analysis:
+ uses: wultra/wultra-infrastructure/.github/workflows/codeql-analysis.yml@develop
+ secrets: inherit
+ with:
+ 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
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
\ No newline at end of file
diff --git a/docs/Database-Structure.md b/docs/Database-Structure.md
new file mode 100644
index 00000000..f4037859
--- /dev/null
+++ b/docs/Database-Structure.md
@@ -0,0 +1,42 @@
+# Database Structure
+
+
+
+You can download DDL scripts for supported databases:
+
+- [PostgreSQL - Create Database Schema](./sql/postgresql/enrollment/create-schema.sql)
+- [Oracle - Create Database Schema](./sql/oracle/enrollment/create-schema.sql)
+
+
+## Auditing
+
+The DDL files contain an `audit_log` table definition. The table differs slightly per database.
+
+Only one `audit_log` table is required per PowerAuth stack in case the same schema is used for all deployed applications.
+
+For more information about auditing library, see the [Wultra auditing library documentation](https://github.com/wultra/lime-java-core#wultra-auditing-library).
+
+
+## Table Documentation
+
+This chapter explains individual tables and their columns. The column types are used from PostgreSQL dialect, other databases use types that are equivalent (mapping is usually straight-forward).
+
+
+### Operation Template Table
+
+Stores operation templates to be shown by the mobile application.
+For more information, see [Operation Extensions](Operation-Extensions.md) and [Customizing Operation Form Data](Operation-Form-Data.md).
+
+#### Schema
+
+| Name | Type | Info | Note |
+|---------------|----------------|------------------------|------------------------------------------------------------------------------------------|
+| `id` | `BIGINT` | `NOT NULL PRIMARY KEY` | Autogenerated record identifier. |
+| `placeholder` | `VARCHAR(255)` | `NOT NULL` | Operation type at PowerAuth server. |
+| `language` | `VARCHAR(8)` | `NOT NULL` | Language of the template. |
+| `title` | `VARCHAR(255)` | `NOT NULL` | Title of the operation. |
+| `message` | `TEXT` | `NOT NULL` | Message for the user related to the operation. |
+| `attributes` | `TEXT` | | Structured custom form data attributes as JSON. |
+| `ui` | `TEXT` | | JSON configuration which may affect behavior or visual aspect of the mobile application. |
+
+
diff --git a/docs/Home.md b/docs/Home.md
index c16d9beb..0b605886 100644
--- a/docs/Home.md
+++ b/docs/Home.md
@@ -6,6 +6,7 @@ PowerAuth Enrollment Server is an easy to deploy backend service used for bootst
- [Deploying Enrollment Server](./Deploying-Enrollment-Server.md)
- [Deploying Enrollment Server on JBoss/Wildfly](./Deploying-Wildfly.md)
+- [Database Structure](./Database-Structure.md)
- [Migration Instructions](./Migration-Instructions.md)
- [Configuration Properties](./Configuration-Properties.md)
- [Documentation for Onboarding Server](./onboarding/Home.md)
diff --git a/docs/PowerAuth-Enrollment-Server-1.5.0.md b/docs/PowerAuth-Enrollment-Server-1.5.0.md
index f8a7ef54..1b28a05f 100644
--- a/docs/PowerAuth-Enrollment-Server-1.5.0.md
+++ b/docs/PowerAuth-Enrollment-Server-1.5.0.md
@@ -24,6 +24,19 @@ Make sure that you use dialect without version.
Since version `1.5.0`, MySQL database is not supported anymore.
+### Oracle
+
+#### Operation Template
+
+In the `1.5.0` version, the `ES_OPERATION_TEMPLATE` table in the **Oracle** database had a change in the data type of the `MESSAGE` and `ATTRIBUTES` columns. They have been altered from `BLOB` to `CLOB`.
+
+You need to execute the following commands to alter the columns:
+
+```sql
+ALTER TABLE ES_OPERATION_TEMPLATE MODIFY (MESSAGE CLOB);
+ALTER TABLE ES_OPERATION_TEMPLATE MODIFY (ATTRIBUTES CLOB);
+```
+
## API Extensions
Since version `1.5.0`, the API supports new cell types in operation responses. These are:
diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md
index f1cb671e..4a723510 100644
--- a/docs/_Sidebar.md
+++ b/docs/_Sidebar.md
@@ -2,6 +2,7 @@
- [Deploying Enrollment Server](./Deploying-Enrollment-Server.md)
- [Deploying Enrollment Server on JBoss/Wildfly](./Deploying-Wildfly.md)
+- [Database Structure](./Database-Structure.md)
- [Migration Instructions](./Migration-Instructions.md)
- [Configuration Properties](./Configuration-Properties.md)
- [Documentation for Onboarding Server](./onboarding/Home.md)
diff --git a/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/20230614-add-sca-result.xml b/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/20230614-add-sca-result.xml
new file mode 100644
index 00000000..54f0fb0c
--- /dev/null
+++ b/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/20230614-add-sca-result.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+ Create a new sequence es_sca_result_seq
+
+
+
+
+
+
+
+
+
+ Create a new table es_onboarding_process
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create a new index on es_sca_result(identity_verification_id)
+
+
+
+
+
+
+
+
+
+
+
+ Create a new index on es_sca_result(process_id)
+
+
+
+
+
+
diff --git a/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/db.changelog-version.xml b/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/db.changelog-version.xml
index 39cdd4d0..25bba1d7 100644
--- a/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/db.changelog-version.xml
+++ b/docs/db/changelog/changesets/enrollment-server-onboarding/1.5.x/db.changelog-version.xml
@@ -5,5 +5,6 @@
+
\ No newline at end of file
diff --git a/docs/onboarding/Database-Structure.md b/docs/onboarding/Database-Structure.md
index fd1317f8..f5c6632c 100644
--- a/docs/onboarding/Database-Structure.md
+++ b/docs/onboarding/Database-Structure.md
@@ -190,3 +190,24 @@ Stores result of document verification.
| `timestamp_created` | `TEXT` | | Timestamp when record was created. |
+
+
+
+### SCA Result Table
+
+Stores result of SCA (Strong Customer Authentication) steps (presence check and OTP verification).
+
+#### Schema
+
+| Name | Type | Info | Note |
+|----------------------------|---------------|--------------------------------------|---------------------------------------------------------------------------------------------------|
+| `id` | `BIGINT` | `NOT NULL PRIMARY KEY` | Autogenerated record identifier (Long). |
+| `identity_verification_id` | `VARCHAR(36)` | `NOT NULL` | Identity verification identifier. |
+| `process_id` | `VARCHAR(36)` | `NOT NULL` | Process identifier (UUID). |
+| `presence_check_result` | `VARCHAR(32)` | | Result of presence check (`SUCCESS`, `FAILED`). |
+| `otp_verification_result` | `VARCHAR(32)` | | Result of the last OTP verification (`SUCCESS`, `FAILED`). |
+| `sca_result` | `VARCHAR(32)` | | Aggregated result of `presence_check_result` and `otp_verification_result` (`SUCCESS`, `FAILED`). |
+| `timestamp_created` | `TIMESTAMP` | `NOT NULL DEFAULT CURRENT_TIMESTAMP` | Timestamp when the SCA was started. |
+| `timestamp_last_updated` | `TIMESTAMP` | | Timestamp when record was last updated. |
+
+
diff --git a/docs/onboarding/PowerAuth-Enrollment-Onboarding-Server-1.5.0.md b/docs/onboarding/PowerAuth-Enrollment-Onboarding-Server-1.5.0.md
index 786fff4b..e50ca51a 100644
--- a/docs/onboarding/PowerAuth-Enrollment-Onboarding-Server-1.5.0.md
+++ b/docs/onboarding/PowerAuth-Enrollment-Onboarding-Server-1.5.0.md
@@ -62,7 +62,7 @@ A new column `total_attempts` has been added to the table `es_onboarding_otp`.
#### PostgreSQL
```sql
-ALTER TABLE es_onboarding_process
+ALTER TABLE es_onboarding_otp
ADD COLUMN TOTAL_ATTEMPTS INTEGER DEFAULT 0;
```
@@ -70,11 +70,64 @@ ALTER TABLE es_onboarding_process
#### Oracle
```sql
-ALTER TABLE es_onboarding_process
+ALTER TABLE es_onboarding_otp
ADD total_attempts INTEGER DEFAULT 0;
```
+### SCA Result
+
+A new table `es_sca_result` has been created.
+
+
+#### PostgreSQL
+
+```sql
+CREATE SEQUENCE es_sca_result_seq INCREMENT BY 50 START WITH 1;
+
+CREATE TABLE es_sca_result
+(
+ id BIGINT NOT NULL PRIMARY KEY,
+ identity_verification_id VARCHAR(36) NOT NULL,
+ process_id VARCHAR(36) NOT NULL,
+ presence_check_result VARCHAR(32),
+ otp_verification_result VARCHAR(32),
+ sca_result VARCHAR(32),
+ timestamp_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ timestamp_last_updated TIMESTAMP,
+ FOREIGN KEY (identity_verification_id) REFERENCES es_identity_verification (id),
+ FOREIGN KEY (process_id) REFERENCES es_onboarding_process (id)
+);
+
+CREATE INDEX identity_verification_id ON es_sca_result (identity_verification_id);
+CREATE INDEX process_id ON es_sca_result (process_id);
+```
+
+
+#### Oracle
+
+```sql
+CREATE SEQUENCE ES_SCA_RESULT_SEQ INCREMENT BY 50 START WITH 1;
+
+CREATE TABLE ES_SCA_RESULT
+(
+ ID NUMBER(19) NOT NULL PRIMARY KEY,
+ IDENTITY_VERIFICATION_ID VARCHAR2(36 CHAR) NOT NULL,
+ PROCESS_ID VARCHAR2(36 CHAR) NOT NULL,
+ PRESENCE_CHECK_RESULT VARCHAR2(32 CHAR),
+ OTP_VERIFICATION_RESULT VARCHAR2(32 CHAR),
+ SCA_RESULT VARCHAR2(32 CHAR),
+ TIMESTAMP_CREATED TIMESTAMP(6) NOT NULL,
+ TIMESTAMP_LAST_UPDATED TIMESTAMP(6),
+ FOREIGN KEY (IDENTITY_VERIFICATION_ID) REFERENCES ES_IDENTITY_VERIFICATION (ID),
+ FOREIGN KEY (PROCESS_ID) REFERENCES ES_ONBOARDING_PROCESS (ID)
+);
+
+CREATE INDEX IDENTITY_VERIFICATION_ID ON ES_SCA_RESULT (IDENTITY_VERIFICATION_ID);
+CREATE INDEX PROCESS_ID ON ES_SCA_RESULT (PROCESS_ID);
+```
+
+
## Dependencies
PostgreSQL JDBC driver is already included in the WAR file.
diff --git a/docs/sql/oracle/onboarding/create-schema.sql b/docs/sql/oracle/onboarding/create-schema.sql
index e04c2559..53f6d4c2 100644
--- a/docs/sql/oracle/onboarding/create-schema.sql
+++ b/docs/sql/oracle/onboarding/create-schema.sql
@@ -158,6 +158,25 @@ CREATE TABLE ES_DOCUMENT_RESULT (
-- Oracle does not create indexes on foreign keys automatically
CREATE INDEX DOCUMENT_VERIF_RESULT ON ES_DOCUMENT_RESULT (DOCUMENT_VERIFICATION_ID);
+CREATE SEQUENCE ES_SCA_RESULT_SEQ INCREMENT BY 50 START WITH 1;
+
+CREATE TABLE ES_SCA_RESULT
+(
+ ID NUMBER(19) NOT NULL PRIMARY KEY,
+ IDENTITY_VERIFICATION_ID VARCHAR2(36 CHAR) NOT NULL,
+ PROCESS_ID VARCHAR2(36 CHAR) NOT NULL,
+ PRESENCE_CHECK_RESULT VARCHAR2(32 CHAR),
+ OTP_VERIFICATION_RESULT VARCHAR2(32 CHAR),
+ SCA_RESULT VARCHAR2(32 CHAR),
+ TIMESTAMP_CREATED TIMESTAMP(6) NOT NULL,
+ TIMESTAMP_LAST_UPDATED TIMESTAMP(6),
+ FOREIGN KEY (IDENTITY_VERIFICATION_ID) REFERENCES ES_IDENTITY_VERIFICATION (ID),
+ FOREIGN KEY (PROCESS_ID) REFERENCES ES_ONBOARDING_PROCESS (ID)
+);
+
+CREATE INDEX IDENTITY_VERIFICATION_ID ON ES_SCA_RESULT (IDENTITY_VERIFICATION_ID);
+CREATE INDEX PROCESS_ID ON ES_SCA_RESULT (PROCESS_ID);
+
-- Scheduler lock table - https://github.com/lukas-krecan/ShedLock#configure-lockprovider
BEGIN EXECUTE IMMEDIATE 'CREATE TABLE shedlock (
name VARCHAR2(64 CHAR) NOT NULL,
diff --git a/docs/sql/postgresql/onboarding/create-schema.sql b/docs/sql/postgresql/onboarding/create-schema.sql
index 30d8fab2..1defbb88 100644
--- a/docs/sql/postgresql/onboarding/create-schema.sql
+++ b/docs/sql/postgresql/onboarding/create-schema.sql
@@ -33,7 +33,7 @@ CREATE TABLE es_onboarding_process (
error_origin VARCHAR(256),
error_score INTEGER NOT NULL DEFAULT 0,
custom_data VARCHAR(1024) NOT NULL,
- fds_data TEXT NOT NULL,
+ fds_data TEXT,
timestamp_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
timestamp_last_updated TIMESTAMP,
timestamp_finished TIMESTAMP,
@@ -162,6 +162,25 @@ CREATE TABLE es_document_result (
-- PostgreSQL does not create indexes on foreign keys automatically
CREATE INDEX document_verif_result ON es_document_result (document_verification_id);
+CREATE SEQUENCE es_sca_result_seq INCREMENT BY 50 START WITH 1;
+
+CREATE TABLE es_sca_result
+(
+ id BIGINT NOT NULL PRIMARY KEY,
+ identity_verification_id VARCHAR(36) NOT NULL,
+ process_id VARCHAR(36) NOT NULL,
+ presence_check_result VARCHAR(32),
+ otp_verification_result VARCHAR(32),
+ sca_result VARCHAR(32),
+ timestamp_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ timestamp_last_updated TIMESTAMP,
+ FOREIGN KEY (identity_verification_id) REFERENCES es_identity_verification (id),
+ FOREIGN KEY (process_id) REFERENCES es_onboarding_process (id)
+);
+
+CREATE INDEX identity_verification_id ON es_sca_result (identity_verification_id);
+CREATE INDEX process_id ON es_sca_result (process_id);
+
-- Scheduler lock table - https://github.com/lukas-krecan/ShedLock#configure-lockprovider
CREATE TABLE IF NOT EXISTS shedlock (
name VARCHAR(64) NOT NULL,
diff --git a/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStartResponse.java b/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStartResponse.java
index 475d405c..5053fdce 100644
--- a/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStartResponse.java
+++ b/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStartResponse.java
@@ -17,6 +17,7 @@
*/
package com.wultra.app.enrollmentserver.api.model.onboarding.response;
+import com.wultra.app.enrollmentserver.api.model.onboarding.response.data.ConfigurationDataDto;
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
import lombok.Data;
@@ -30,5 +31,6 @@ public class OnboardingStartResponse {
private String processId;
private OnboardingStatus onboardingStatus;
+ private ConfigurationDataDto config;
}
diff --git a/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStatusResponse.java b/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStatusResponse.java
index f69ffb42..cc9c2f50 100644
--- a/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStatusResponse.java
+++ b/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/OnboardingStatusResponse.java
@@ -17,6 +17,7 @@
*/
package com.wultra.app.enrollmentserver.api.model.onboarding.response;
+import com.wultra.app.enrollmentserver.api.model.onboarding.response.data.ConfigurationDataDto;
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
import lombok.Data;
@@ -30,5 +31,6 @@ public class OnboardingStatusResponse {
private String processId;
private OnboardingStatus onboardingStatus;
+ private ConfigurationDataDto config;
}
diff --git a/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/data/ConfigurationDataDto.java b/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/data/ConfigurationDataDto.java
index 2ea9885a..de22bbb5 100644
--- a/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/data/ConfigurationDataDto.java
+++ b/enrollment-server-onboarding-api-model/src/main/java/com/wultra/app/enrollmentserver/api/model/onboarding/response/data/ConfigurationDataDto.java
@@ -16,6 +16,7 @@
*/
package com.wultra.app.enrollmentserver.api.model.onboarding.response.data;
+import lombok.Builder;
import lombok.Data;
/**
@@ -24,11 +25,24 @@
* @author Lukas Lukovsky, lukas.lukovsky@wultra.com
*/
@Data
+@Builder
public class ConfigurationDataDto {
/**
- * OTP resend period (ISO 8601 format)
+ * OTP resend period (ISO 8601 format).
+ *
+ * @deprecated ISO 8601 format does not have native support on mobile platforms,
+ * leading to complexities in mobile client processing. Use {@link #otpResendPeriodSeconds}
+ * for a simpler representation in seconds. Scheduled for removal in future versions.
+ * See https://github.com/wultra/enrollment-server/issues/829 for more details.
+ * TODO (@jandusil, 2023-08-12, #829)
*/
+ @Deprecated
private String otpResendPeriod;
+ /**
+ * OTP resend period in seconds for easier parsing on mobile platforms.
+ */
+ private long otpResendPeriodSeconds;
+
}
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/OnboardingOtpRepository.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/OnboardingOtpRepository.java
index 52f956c1..48d1cce2 100644
--- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/OnboardingOtpRepository.java
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/OnboardingOtpRepository.java
@@ -98,7 +98,4 @@ public interface OnboardingOtpRepository extends CrudRepository.
+ */
+
+package com.wultra.app.onboardingserver.common.database;
+
+import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
+import com.wultra.app.onboardingserver.common.database.entity.ScaResultEntity;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * Repository for {@link ScaResultEntity}.
+ *
+ * @author Lubos Racansky, lubos.racansky@wultra.com
+ */
+@Repository
+public interface ScaResultRepository extends CrudRepository {
+
+ /**
+ * Count SCA attempts for the given identity verification.
+ *
+ * @param identityVerification Identity verification to identify SCA attempts.
+ * @return count of SCA attempts
+ */
+ int countByIdentityVerification(IdentityVerificationEntity identityVerification);
+
+ /**
+ * Find the latest SCA attempt.
+ *
+ * @param identityVerification Identity verification to identify SCA attempts.
+ * @return the latest SCA attempt or empty
+ */
+ Optional findTopByIdentityVerificationOrderByTimestampCreatedDesc(IdentityVerificationEntity identityVerification);
+}
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentDataEntity.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentDataEntity.java
index 539e815a..f43db040 100644
--- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentDataEntity.java
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentDataEntity.java
@@ -24,6 +24,7 @@
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.UuidGenerator;
import java.io.Serial;
import java.io.Serializable;
@@ -47,8 +48,8 @@ public class DocumentDataEntity implements Serializable {
private static final long serialVersionUID = 7685715667785423079L;
@Id
- @GeneratedValue(generator = "uuid")
- @GenericGenerator(name = "uuid", strategy = "uuid2")
+ @GeneratedValue
+ @UuidGenerator
@Column(name = "id", nullable = false)
private String id;
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentVerificationEntity.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentVerificationEntity.java
index 22358a5b..38ea3173 100644
--- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentVerificationEntity.java
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/DocumentVerificationEntity.java
@@ -25,6 +25,7 @@
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.UuidGenerator;
import java.io.Serial;
import java.io.Serializable;
@@ -53,8 +54,8 @@ public class DocumentVerificationEntity implements Serializable {
* Autogenerated identifier
*/
@Id
- @GeneratedValue(generator = "uuid")
- @GenericGenerator(name = "uuid", strategy = "uuid2")
+ @GeneratedValue
+ @UuidGenerator
@Column(name = "id", nullable = false)
private String id;
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/IdentityVerificationEntity.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/IdentityVerificationEntity.java
index d742717c..c6bb5c5c 100644
--- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/IdentityVerificationEntity.java
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/IdentityVerificationEntity.java
@@ -28,6 +28,7 @@
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.UuidGenerator;
import java.io.Serial;
import java.io.Serializable;
@@ -60,8 +61,8 @@ public class IdentityVerificationEntity implements Serializable {
public static final String CLIENT_EVALUATION_FAILED = "clientEvaluationFailed";
@Id
- @GeneratedValue(generator = "uuid")
- @GenericGenerator(name = "uuid", strategy = "uuid2")
+ @GeneratedValue
+ @UuidGenerator
@Column(name = "id", nullable = false)
private String id;
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingOtpEntity.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingOtpEntity.java
index f58c9219..3b4c9783 100644
--- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingOtpEntity.java
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingOtpEntity.java
@@ -27,6 +27,7 @@
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.UuidGenerator;
import java.io.Serial;
import java.io.Serializable;
@@ -55,8 +56,8 @@ public class OnboardingOtpEntity implements Serializable {
public static final String ERROR_MAX_FAILED_ATTEMPTS = "maxFailedAttemptsOtp";
@Id
- @GeneratedValue(generator = "uuid")
- @GenericGenerator(name = "uuid", strategy = "uuid2")
+ @GeneratedValue
+ @UuidGenerator
@Column(name = "id", nullable = false)
private String id;
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingProcessEntity.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingProcessEntity.java
index 8dc6eec1..ca38c0d2 100644
--- a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingProcessEntity.java
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/OnboardingProcessEntity.java
@@ -25,7 +25,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
-import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.UuidGenerator;
import java.io.Serial;
import java.io.Serializable;
@@ -60,8 +60,8 @@ public class OnboardingProcessEntity implements Serializable {
public static final String ERROR_USER_LOOKUP = "userLookupFailed";
@Id
- @GeneratedValue(generator = "uuid")
- @GenericGenerator(name = "uuid", strategy = "uuid2")
+ @GeneratedValue
+ @UuidGenerator
@Column(name = "id", nullable = false)
private String id;
diff --git a/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/ScaResultEntity.java b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/ScaResultEntity.java
new file mode 100644
index 00000000..c0452fde
--- /dev/null
+++ b/enrollment-server-onboarding-common/src/main/java/com/wultra/app/onboardingserver/common/database/entity/ScaResultEntity.java
@@ -0,0 +1,105 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2023 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.onboardingserver.common.database.entity;
+
+import jakarta.persistence.*;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * SCA (Strong Customer Authentication) consists of presence check and OTP verification.
+ * This entity stores particular and also aggregated results.
+ * One SCA attempt is represented by a single entry.
+ *
+ * @author Lubos Racansky, lubos.racansky@wultra.com
+ */
+@Getter
+@Setter
+@Entity
+@Table(name = "es_sca_result")
+@ToString
+@EqualsAndHashCode(of = "id")
+public class ScaResultEntity implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Autogenerated identifier
+ */
+ @Id
+ @SequenceGenerator(name = "es_sca_result", sequenceName = "es_sca_result_seq")
+ @GeneratedValue(strategy = GenerationType.AUTO, generator = "es_sca_result")
+ private Long id;
+
+ /**
+ * Identifier of the related identity verification entity.
+ */
+ @ManyToOne
+ @JoinColumn(name = "identity_verification_id", referencedColumnName = "id", nullable = false)
+ private IdentityVerificationEntity identityVerification;
+
+ @Column(name = "process_id", nullable = false)
+ private String processId;
+
+ /**
+ * Result of presence check.
+ */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "presence_check_result")
+ private Result presenceCheckResult;
+
+ /**
+ * Result of the last OTP verification.
+ */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "otp_verification_result")
+ private Result otpVerificationResult;
+
+ /**
+ * Aggregated result of {@link #presenceCheckResult} and {@link #otpVerificationResult}.
+ */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "sca_result")
+ private Result scaResult;
+
+ /**
+ * Timestamp when the entity was created.
+ */
+ @Column(name = "timestamp_created", nullable = false)
+ private Date timestampCreated;
+
+ /**
+ * Timestamp when the entity was last updated.
+ */
+ @Column(name = "timestamp_last_updated")
+ private Date timestampLastUpdated;
+
+ public enum Result {
+ SUCCESS,
+ FAILED
+ }
+
+}
diff --git a/enrollment-server-onboarding/pom.xml b/enrollment-server-onboarding/pom.xml
index 1ef9043e..d46c044d 100644
--- a/enrollment-server-onboarding/pom.xml
+++ b/enrollment-server-onboarding/pom.xml
@@ -146,12 +146,6 @@
test
-
- org.apache.tomcat.embed
- tomcat-embed-el
- test
-
-
org.springdoc
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/IdentityVerificationController.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/IdentityVerificationController.java
index a6acc6bc..094de983 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/IdentityVerificationController.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/IdentityVerificationController.java
@@ -27,14 +27,14 @@
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
import io.getlime.core.rest.model.base.response.Response;
-import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope;
import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureTypes;
import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuth;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthToken;
import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication;
-import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthAuthenticationException;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException;
import io.getlime.security.powerauth.rest.api.spring.exception.authentication.PowerAuthTokenInvalidException;
@@ -118,7 +118,7 @@ public ObjectResponse checkIdentityVerificat
/**
* Submit identity-related documents for verification.
* @param request Document submit request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Document submit response.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
* @throws PowerAuthEncryptionException Thrown when request decryption fails.
@@ -130,22 +130,22 @@ public ObjectResponse checkIdentityVerificat
* @throws OnboardingProcessLimitException Thrown when maximum failed attempts for identity verification have been reached.
*/
@PostMapping("document/submit")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
@PowerAuthToken(signatureType = {
PowerAuthSignatureTypes.POSSESSION
})
public Response submitDocuments(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext,
+ @Parameter(hidden = true) EncryptionContext encryptionContext,
@Parameter(hidden = true) PowerAuthApiAuthentication apiAuthentication)
throws PowerAuthAuthenticationException, PowerAuthEncryptionException, DocumentSubmitException, OnboardingProcessException, IdentityVerificationLimitException, RemoteCommunicationException, IdentityVerificationException, OnboardingProcessLimitException {
- return identityVerificationRestService.submitDocuments(request, eciesContext, apiAuthentication);
+ return identityVerificationRestService.submitDocuments(request, encryptionContext, apiAuthentication);
}
/**
* Upload a single document related to identity verification. This endpoint is used for upload of large documents.
* @param requestData Binary request data.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Document upload response.
* @throws IdentityVerificationException Thrown when identity verification was not found.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
@@ -154,16 +154,16 @@ public Response submitDocuments(@EncryptedRequestBody ObjectRequest uploadDocument(@EncryptedRequestBody byte[] requestData,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext,
+ @Parameter(hidden = true) EncryptionContext encryptionContext,
@Parameter(hidden = true) PowerAuthApiAuthentication apiAuthentication)
throws IdentityVerificationException, PowerAuthAuthenticationException, PowerAuthEncryptionException, DocumentVerificationException, OnboardingProcessException {
- return identityVerificationRestService.uploadDocument(requestData, eciesContext, apiAuthentication);
+ return identityVerificationRestService.uploadDocument(requestData, encryptionContext, apiAuthentication);
}
/**
@@ -189,7 +189,7 @@ public ObjectResponse checkDocumentStatus(@RequestBody O
/**
* Initialize document verification SDK for an integration.
* @param request Presence check initialization request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param apiAuthentication PowerAuth authentication.
* @return Verification SDK initialization response.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
@@ -199,24 +199,24 @@ public ObjectResponse checkDocumentStatus(@RequestBody O
* @throws RemoteCommunicationException In case of remote communication error.
*/
@PostMapping("document/init-sdk")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
@PowerAuth(resourceId = "/api/identity/document/init-sdk", signatureType = {
PowerAuthSignatureTypes.POSSESSION
})
public ObjectResponse initVerificationSdk(
@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext,
+ @Parameter(hidden = true) EncryptionContext encryptionContext,
@Parameter(hidden = true) PowerAuthApiAuthentication apiAuthentication)
throws PowerAuthAuthenticationException, DocumentVerificationException, PowerAuthEncryptionException, OnboardingProcessException, RemoteCommunicationException {
- return identityVerificationRestService.initVerificationSdk(request, eciesContext, apiAuthentication);
+ return identityVerificationRestService.initVerificationSdk(request, encryptionContext, apiAuthentication);
}
/**
* Initialize presence check process.
*
* @param request Presence check initialization request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param apiAuthentication PowerAuth authentication.
* @return Presence check initialization response.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
@@ -225,16 +225,16 @@ public ObjectResponse initVerificationSdk(
* @throws OnboardingProcessException Thrown when onboarding process is invalid.
*/
@PostMapping("presence-check/init")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
@PowerAuth(resourceId = "/api/identity/presence-check/init", signatureType = {
PowerAuthSignatureTypes.POSSESSION
})
public ResponseEntity initPresenceCheck(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext,
+ @Parameter(hidden = true) EncryptionContext encryptionContext,
@Parameter(hidden = true) PowerAuthApiAuthentication apiAuthentication)
throws IdentityVerificationException, PowerAuthAuthenticationException, PowerAuthEncryptionException, OnboardingProcessException {
- return identityVerificationRestService.initPresenceCheck(request, eciesContext, apiAuthentication);
+ return identityVerificationRestService.initPresenceCheck(request, encryptionContext, apiAuthentication);
}
/**
@@ -278,18 +278,18 @@ public ResponseEntity resendOtp(
/**
* Verify an OTP code received from the user.
* @param request Presence check initialization request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Send OTP response.
* @throws PowerAuthEncryptionException Thrown when request decryption fails.
* @throws OnboardingProcessException Thrown when onboarding process is not found.
*/
@PostMapping("otp/verify")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
public ObjectResponse verifyOtp(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext)
+ @Parameter(hidden = true) EncryptionContext encryptionContext)
throws PowerAuthEncryptionException, OnboardingProcessException {
- return identityVerificationRestService.verifyOtp(request, eciesContext);
+ return identityVerificationRestService.verifyOtp(request, encryptionContext);
}
/**
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingController.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingController.java
index a8014296..08e29d83 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingController.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingController.java
@@ -33,10 +33,10 @@
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
import io.getlime.core.rest.model.base.response.Response;
-import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope;
import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption;
-import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletRequest;
@@ -78,7 +78,7 @@ public OnboardingController(final OnboardingServiceImpl onboardingService) {
* Start an onboarding process.
*
* @param request Start onboarding process request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param servletRequest HttpServletRequest.
* @return Start onboarding process response.
* @throws PowerAuthEncryptionException Thrown when request is invalid.
@@ -88,14 +88,14 @@ public OnboardingController(final OnboardingServiceImpl onboardingService) {
* @throws InvalidRequestObjectException Thrown in case request is invalid.
*/
@PostMapping("start")
- @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE)
public ObjectResponse startOnboarding(
@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext,
+ @Parameter(hidden = true) EncryptionContext encryptionContext,
final HttpServletRequest servletRequest) throws OnboardingProcessException, OnboardingOtpDeliveryException, PowerAuthEncryptionException, TooManyProcessesException, InvalidRequestObjectException {
// Check if the request was correctly decrypted
- if (eciesContext == null) {
+ if (encryptionContext == null) {
throw new PowerAuthEncryptionException("ECIES decryption failed during onboarding");
}
@@ -113,18 +113,18 @@ public ObjectResponse startOnboarding(
* Resend an onboarding OTP code.
*
* @param request Resend an OTP code request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Response.
* @throws PowerAuthEncryptionException Thrown when request decryption fails.
* @throws OnboardingProcessException Thrown when onboarding process fails.
* @throws OnboardingOtpDeliveryException Thrown when onboarding OTP delivery fails.
*/
@PostMapping("otp/resend")
- @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE)
public Response resendOtp(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException, OnboardingProcessException, OnboardingOtpDeliveryException {
+ @Parameter(hidden = true) EncryptionContext encryptionContext) throws PowerAuthEncryptionException, OnboardingProcessException, OnboardingOtpDeliveryException {
// Check if the request was correctly decrypted
- if (eciesContext == null) {
+ if (encryptionContext == null) {
throw new PowerAuthEncryptionException("ECIES decryption failed while resending OTP code");
}
@@ -139,17 +139,17 @@ public Response resendOtp(@EncryptedRequestBody ObjectRequest getStatus(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException, OnboardingProcessException {
+ @Parameter(hidden = true) EncryptionContext encryptionContext) throws PowerAuthEncryptionException, OnboardingProcessException {
// Check if the request was correctly decrypted
- if (eciesContext == null) {
+ if (encryptionContext == null) {
throw new PowerAuthEncryptionException("ECIES decryption failed while getting status");
}
@@ -166,17 +166,17 @@ public ObjectResponse getStatus(@EncryptedRequestBody
* Perform cleanup related to an onboarding process.
*
* @param request Onboarding cleanup request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Onboarding cleanup response.
* @throws PowerAuthEncryptionException Thrown when request decryption fails.
* @throws OnboardingProcessException Thrown when onboarding process is not found.
*/
@PostMapping("cleanup")
- @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE)
public Response performCleanup(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException, OnboardingProcessException {
+ @Parameter(hidden = true) EncryptionContext encryptionContext) throws PowerAuthEncryptionException, OnboardingProcessException {
// Check if the request was correctly decrypted
- if (eciesContext == null) {
+ if (encryptionContext == null) {
throw new PowerAuthEncryptionException("ECIES decryption failed during cleanup");
}
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingOtpController.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingOtpController.java
index e3d71be3..826ba824 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingOtpController.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/controller/api/OnboardingOtpController.java
@@ -28,10 +28,10 @@
import com.wultra.app.onboardingserver.mock.model.response.OtpDetailResponse;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
-import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope;
import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption;
-import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,16 +64,16 @@ public OnboardingOtpController(OnboardingOtpRepository onboardingOtpRepository)
* Get onboarding OTP detail for tests.
*
* @param request OTP detail request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return OTP detail response.
* @throws PowerAuthEncryptionException In case encryption or decryption fails.
*/
@RequestMapping(value = "otp/detail", method = RequestMethod.POST)
- @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE)
public ObjectResponse getOtpDetail(@EncryptedRequestBody ObjectRequest request,
- EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException, OnboardingProcessException {
+ EncryptionContext encryptionContext) throws PowerAuthEncryptionException, OnboardingProcessException {
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationFinishService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationFinishService.java
index 2136028e..020a4780 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationFinishService.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationFinishService.java
@@ -123,7 +123,12 @@ private void sendFinishedEvent(final OnboardingProcessEntity process, final Iden
try {
logger.info("Publishing finish event, {}", ownerId);
final ProcessEventResponse response = onboardingProvider.processEvent(request);
- logger.info("Finish event published: {}, {}", response, ownerId);
+ logger.debug("Got {} for processId={}", response, request.getProcessId());
+ if (response.isErrorOccurred()) {
+ logger.info("Finish event failed to published: {}, {}", response.getErrorDetail(), ownerId);
+ } else {
+ logger.info("Finish event published, {}", ownerId);
+ }
} catch (OnboardingProviderException e) {
// unsuccessful event publishing does not stop the process
logger.info("Unable to publish finished event to the onboarding adapter: {}", e.getMessage());
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationOtpService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationOtpService.java
index 16e6d24a..a4b48ac3 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationOtpService.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationOtpService.java
@@ -17,17 +17,14 @@
*/
package com.wultra.app.onboardingserver.impl.service;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.wultra.app.enrollmentserver.api.model.onboarding.response.OtpVerifyResponse;
import com.wultra.app.enrollmentserver.model.enumeration.*;
import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.onboardingserver.common.database.IdentityVerificationRepository;
import com.wultra.app.onboardingserver.common.database.OnboardingOtpRepository;
import com.wultra.app.onboardingserver.common.database.OnboardingProcessRepository;
-import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
-import com.wultra.app.onboardingserver.common.database.entity.OnboardingOtpEntity;
-import com.wultra.app.onboardingserver.common.database.entity.OnboardingProcessEntity;
-import com.wultra.app.onboardingserver.common.database.entity.OnboardingProcessEntityWrapper;
+import com.wultra.app.onboardingserver.common.database.ScaResultRepository;
+import com.wultra.app.onboardingserver.common.database.entity.*;
import com.wultra.app.onboardingserver.common.enumeration.OnboardingProcessError;
import com.wultra.app.onboardingserver.common.errorhandling.OnboardingProcessException;
import com.wultra.app.onboardingserver.common.service.AuditService;
@@ -37,8 +34,8 @@
import com.wultra.app.onboardingserver.errorhandling.OnboardingProviderException;
import com.wultra.app.onboardingserver.provider.OnboardingProvider;
import com.wultra.app.onboardingserver.provider.model.request.SendOtpCodeRequest;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -56,67 +53,27 @@
*/
@Service
@Slf4j
+@AllArgsConstructor
public class IdentityVerificationOtpService {
private final OnboardingProcessRepository onboardingProcessRepository;
private final OnboardingOtpRepository onboardingOtpRepository;
private final OtpServiceImpl otpService;
- private OnboardingProvider onboardingProvider;
+ private final OnboardingProvider onboardingProvider;
private final OnboardingProcessLimitService processLimitService;
private final IdentityVerificationRepository identityVerificationRepository;
+ private final ScaResultRepository scaResultRepository;
+
private final IdentityVerificationConfig identityVerificationConfig;
private final IdentityVerificationService identityVerificationService;
private final AuditService auditService;
- private final ObjectMapper mapper = new ObjectMapper();
-
- /**
- * Service constructor.
- *
- * @param onboardingProcessRepository Onboarding process repository.
- * @param onboardingOtpRepository Onboarding OTP repository.
- * @param otpService OTP service.
- * @param processLimitService Process limit service.
- * @param identityVerificationRepository Identity verification repository.
- * @param identityVerificationConfig Identity verification config.
- * @param identityVerificationService Identity verification service.
- * @param auditService Audit service.
- */
- public IdentityVerificationOtpService(
- final OnboardingProcessRepository onboardingProcessRepository,
- final OnboardingOtpRepository onboardingOtpRepository,
- final OtpServiceImpl otpService,
- final OnboardingProcessLimitService processLimitService,
- final IdentityVerificationRepository identityVerificationRepository,
- final IdentityVerificationConfig identityVerificationConfig,
- final IdentityVerificationService identityVerificationService,
- final AuditService auditService) {
-
- this.onboardingProcessRepository = onboardingProcessRepository;
- this.onboardingOtpRepository = onboardingOtpRepository;
- this.otpService = otpService;
- this.processLimitService = processLimitService;
- this.identityVerificationRepository = identityVerificationRepository;
- this.identityVerificationConfig = identityVerificationConfig;
- this.identityVerificationService = identityVerificationService;
- this.auditService = auditService;
- }
-
- /**
- * Set onboarding provider via setter injection.
- * @param onboardingProvider Onboarding provider.
- */
- @Autowired(required = false)
- public void setOnboardingProvider(OnboardingProvider onboardingProvider) {
- this.onboardingProvider = onboardingProvider;
- }
-
/**
* Resends an OTP code for a process during identity verification.
* @param identityVerification Identity verification entity.
@@ -165,7 +122,10 @@ public OtpVerifyResponse verifyOtpCode(String processId, OwnerId ownerId, String
}
if (!response.isVerified()) {
logger.info("SCA failed, wrong OTP code, process ID: {}", processId);
+ saveScaOtpResult(ScaResultEntity.Result.FAILED, process, ownerId);
return response;
+ } else {
+ saveScaOtpResult(ScaResultEntity.Result.SUCCESS, process, ownerId);
}
return verifyPresenceCheck(process, response, ownerId);
}
@@ -182,6 +142,16 @@ public boolean isUserVerifiedUsingOtp(String processId) {
.isPresent();
}
+ private void saveScaOtpResult(final ScaResultEntity.Result otpResult, final OnboardingProcessEntity process, final OwnerId ownerId) throws OnboardingProcessException {
+ final IdentityVerificationEntity identityVerification = getIdentityVerificationEntity(process);
+ logger.debug("Saving SCA otp verification result: {}, identity verification ID: {}, {}", otpResult, identityVerification.getId(), ownerId);
+ final ScaResultEntity scaResult = scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(identityVerification).orElseThrow(() ->
+ new OnboardingProcessException("No ScaResult found, process ID: %s, identity verification ID: %s".formatted(process.getId(), identityVerification.getId())));
+ scaResult.setOtpVerificationResult(otpResult);
+ scaResult.setTimestampLastUpdated(new Date());
+ scaResultRepository.save(scaResult);
+ }
+
private OtpVerifyResponse verifyPresenceCheck(final OnboardingProcessEntity process, final OtpVerifyResponse response, final OwnerId ownerId) throws OnboardingProcessException {
final String processId = process.getId();
if (!identityVerificationConfig.isPresenceCheckEnabled()) {
@@ -193,17 +163,21 @@ private OtpVerifyResponse verifyPresenceCheck(final OnboardingProcessEntity proc
final IdentityVerificationEntity idVerification = getIdentityVerificationEntity(process);
final String errorDetail = idVerification.getErrorDetail();
- final ErrorOrigin errorOrigin = idVerification.getErrorOrigin();
final String rejectReason = idVerification.getRejectReason();
- final RejectOrigin rejectOrigin = idVerification.getRejectOrigin();
- // TODO (racansky, 2022-10-18, #453) this is quite fragile condition, could be improved e.g. with data historization
- if (errorOrigin == ErrorOrigin.PRESENCE_CHECK || rejectOrigin == RejectOrigin.PRESENCE_CHECK) {
+ final ScaResultEntity scaResult = scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(idVerification).orElseThrow(() ->
+ new OnboardingProcessException("No ScaResult found, process ID: %s, identity verification ID: %s".formatted(process.getId(), idVerification.getId())));
+
+ if (scaResult.getPresenceCheckResult() != ScaResultEntity.Result.SUCCESS) {
logger.info("SCA failed, identity verification ID: {}, {} contains errorDetail: {}, rejectReason: {} from previous step",
idVerification.getId(), ownerId, errorDetail, rejectReason);
+ scaResult.setScaResult(ScaResultEntity.Result.FAILED);
+ scaResult.setTimestampLastUpdated(new Date());
return moveToPhasePresenceCheck(process, response, idVerification, ownerId);
} else {
logger.debug("PRESENCE_CHECK without error or reject origin, {}", ownerId);
+ scaResult.setScaResult(ScaResultEntity.Result.SUCCESS);
+ scaResult.setTimestampLastUpdated(new Date());
}
return response;
}
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheck.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheck.java
index 6adaaab8..6f774321 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheck.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheck.java
@@ -20,13 +20,16 @@
import com.wultra.app.enrollmentserver.model.enumeration.*;
import com.wultra.app.onboardingserver.common.database.DocumentVerificationRepository;
import com.wultra.app.onboardingserver.common.database.OnboardingOtpRepository;
+import com.wultra.app.onboardingserver.common.database.ScaResultRepository;
import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.OnboardingOtpEntity;
+import com.wultra.app.onboardingserver.common.database.entity.ScaResultEntity;
import com.wultra.app.onboardingserver.common.errorhandling.RemoteCommunicationException;
import com.wultra.app.onboardingserver.configuration.IdentityVerificationConfig;
import com.wultra.app.onboardingserver.statemachine.guard.document.RequiredDocumentTypesCheck;
import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -49,6 +52,7 @@
// TODO (racansky, 2022-10-14) consider make it Guard for Spring State Machine
@Component
@Slf4j
+@AllArgsConstructor
class IdentityVerificationPrecompleteCheck {
private final IdentityVerificationConfig identityVerificationConfig;
@@ -57,23 +61,12 @@ class IdentityVerificationPrecompleteCheck {
private final OnboardingOtpRepository onboardingOtpRepository;
+ private final ScaResultRepository scaResultRepository;
+
private final DocumentVerificationRepository documentVerificationRepository;
private final ActivationService activationService;
- IdentityVerificationPrecompleteCheck(
- final IdentityVerificationConfig identityVerificationConfig,
- final RequiredDocumentTypesCheck requiredDocumentTypesCheck,
- final OnboardingOtpRepository onboardingOtpRepository,
- final DocumentVerificationRepository documentVerificationRepository,
- final ActivationService activationService) {
- this.identityVerificationConfig = identityVerificationConfig;
- this.requiredDocumentTypesCheck = requiredDocumentTypesCheck;
- this.onboardingOtpRepository = onboardingOtpRepository;
- this.documentVerificationRepository = documentVerificationRepository;
- this.activationService = activationService;
- }
-
/**
* Evaluate all precomplete conditions.
*
@@ -85,44 +78,55 @@ Result evaluate(final IdentityVerificationEntity idVerification) throws RemoteCo
.findAllDocumentVerifications(idVerification, DocumentStatus.ALL_PROCESSED);
final String processId = idVerification.getProcessId();
+ final String identityVerificationId = idVerification.getId();
if (!documentVerifications.stream()
.map(DocumentVerificationEntity::getStatus)
.allMatch(it -> it == DocumentStatus.ACCEPTED)) {
- logger.debug("Some documents are not accepted for identity verification ID: {}", processId);
+ logger.debug("Some documents are not accepted for identity verification ID: {}, process ID: {}", identityVerificationId, processId);
return Result.failed("Some documents not accepted");
}
- if (!requiredDocumentTypesCheck.evaluate(documentVerifications, idVerification.getId())) {
- logger.debug("Not all required documents are present for verification ID: {}", processId);
+ if (!requiredDocumentTypesCheck.evaluate(documentVerifications, identityVerificationId)) {
+ logger.debug("Not all required documents are present for verification ID: {}, process ID: {}", identityVerificationId, processId);
return Result.failed("Required documents not present");
}
if (!isPrecompletePhaseAndStateValid(idVerification)) {
- logger.debug("Not valid phase and state for verification ID: {}", processId);
+ logger.debug("Not valid phase and state for verification ID: {}, process ID: {}", identityVerificationId, processId);
return Result.failed("Not valid phase and state");
}
if (!isVerificationOtpValid(idVerification)) {
- logger.debug("Not valid user verification OTP for verification ID: {}", processId);
+ logger.debug("Not valid user verification OTP for verification ID: {}, process ID: {}", identityVerificationId, processId);
return Result.failed("Not valid user verification OTP");
}
if (!isActivationOtpValid(idVerification)) {
- logger.debug("Not valid activation OTP for verification ID: {}", processId);
+ logger.debug("Not valid activation OTP for verification ID: {}, process ID:{}", identityVerificationId, processId);
return Result.failed("Not valid activation OTP");
}
if (!isActivationValid(idVerification)) {
- logger.debug("Activation is not valid for verification ID: {}", processId);
+ logger.debug("Activation is not valid for verification ID: {}, process ID: {}", identityVerificationId, processId);
return Result.failed("Activation is not valid");
}
- // TODO (racansky, 2022-10-18, #453) validate presence check when the data available
+ if (!isVerificationPassedSca(idVerification)) {
+ logger.debug("Did not pass SCA for verification ID: {}, process ID: {}", identityVerificationId, processId);
+ return Result.failed("Did not pass SCA");
+ }
return Result.successful();
}
+ private boolean isVerificationPassedSca(final IdentityVerificationEntity idVerification) {
+ return scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(idVerification)
+ .map(ScaResultEntity::getScaResult)
+ .filter(it -> it == ScaResultEntity.Result.SUCCESS)
+ .isPresent();
+ }
+
private boolean isActivationValid(IdentityVerificationEntity idVerification) throws RemoteCommunicationException {
final ActivationStatus activationStatus = activationService.fetchActivationStatus(idVerification.getActivationId());
return activationStatus == ActivationStatus.ACTIVE;
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationRestService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationRestService.java
index 6a5f3b0e..2276b5d4 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationRestService.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationRestService.java
@@ -45,7 +45,7 @@
import io.getlime.core.rest.model.base.response.ObjectResponse;
import io.getlime.core.rest.model.base.response.Response;
import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication;
-import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthAuthenticationException;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException;
import io.getlime.security.powerauth.rest.api.spring.exception.authentication.PowerAuthTokenInvalidException;
@@ -125,8 +125,10 @@ public IdentityVerificationRestService(
this.presenceCheckService = presenceCheckService;
this.stateMachineService = stateMachineService;
- this.integrationConfigDto = new ConfigurationDataDto();
- integrationConfigDto.setOtpResendPeriod(onboardingConfig.getOtpResendPeriod().toString());
+ this.integrationConfigDto = ConfigurationDataDto.builder()
+ .otpResendPeriod(onboardingConfig.getOtpResendPeriod().toString())
+ .otpResendPeriodSeconds(onboardingConfig.getOtpResendPeriod().toSeconds())
+ .build();
}
/**
@@ -194,7 +196,7 @@ public ObjectResponse checkIdentityVerificat
/**
* Submit identity-related documents for verification.
* @param request Document submit request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Document submit response.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
* @throws PowerAuthEncryptionException Thrown when request decryption fails.
@@ -207,17 +209,17 @@ public ObjectResponse checkIdentityVerificat
*/
@Transactional
public Response submitDocuments(ObjectRequest request,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication apiAuthentication)
throws PowerAuthAuthenticationException, PowerAuthEncryptionException, DocumentSubmitException, OnboardingProcessException, IdentityVerificationLimitException, RemoteCommunicationException, IdentityVerificationException, OnboardingProcessLimitException {
final String operationDescription = "submitting documents for verification";
checkApiAuthentication(apiAuthentication, operationDescription);
- checkEciesContext(eciesContext, operationDescription);
+ checkEncryptionContext(encryptionContext, operationDescription);
checkRequestObject(request, operationDescription);
// Extract user ID from onboarding process for current activation
- final OwnerId ownerId = extractOwnerId(eciesContext);
+ final OwnerId ownerId = extractOwnerId(encryptionContext);
final String processId = request.getRequestObject().getProcessId();
logger.debug("Onboarding process will be locked using PESSIMISTIC_WRITE lock, {}", processId);
@@ -231,7 +233,7 @@ public Response submitDocuments(ObjectRequest request,
/**
* Upload a single document related to identity verification. This endpoint is used for upload of large documents.
* @param requestData Binary request data.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Document upload response.
* @throws IdentityVerificationException Thrown when identity verification was not found.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
@@ -240,17 +242,17 @@ public Response submitDocuments(ObjectRequest request,
* @throws OnboardingProcessException Thrown when finished onboarding process is not found.
*/
public ObjectResponse uploadDocument(byte[] requestData,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication apiAuthentication)
throws IdentityVerificationException, PowerAuthAuthenticationException, PowerAuthEncryptionException, DocumentVerificationException, OnboardingProcessException {
final String operationDescription = "uploading document for verification";
checkApiAuthentication(apiAuthentication, operationDescription);
- checkEciesContext(eciesContext, operationDescription);
+ checkEncryptionContext(encryptionContext, operationDescription);
checkRequest(requestData, operationDescription);
// Extract user ID from onboarding process for current activation
- final OwnerId ownerId = extractOwnerId(eciesContext);
+ final OwnerId ownerId = extractOwnerId(encryptionContext);
logger.debug("Onboarding process will not be locked, {}", ownerId);
IdentityVerificationEntity idVerification = identityVerificationService.findBy(ownerId);
@@ -294,7 +296,7 @@ public ObjectResponse checkDocumentStatus(ObjectRequest<
/**
* Initialize document verification SDK for an integration.
* @param request Presence check initialization request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param apiAuthentication PowerAuth authentication.
* @return Verification SDK initialization response.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
@@ -305,13 +307,13 @@ public ObjectResponse checkDocumentStatus(ObjectRequest<
*/
public ObjectResponse initVerificationSdk(
ObjectRequest request,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication apiAuthentication)
throws PowerAuthAuthenticationException, DocumentVerificationException, PowerAuthEncryptionException, OnboardingProcessException, RemoteCommunicationException {
final String operationDescription = "initializing document verification SDK";
checkApiAuthentication(apiAuthentication, operationDescription);
- checkEciesContext(eciesContext, operationDescription);
+ checkEncryptionContext(encryptionContext, operationDescription);
checkRequestObject(request, operationDescription);
final OwnerId ownerId = PowerAuthUtil.getOwnerId(apiAuthentication);
@@ -332,7 +334,7 @@ public ObjectResponse initVerificationSdk(
* Initialize presence check process.
*
* @param request Presence check initialization request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param apiAuthentication PowerAuth authentication.
* @return Presence check initialization response.
* @throws PowerAuthAuthenticationException Thrown when request authentication fails.
@@ -342,13 +344,13 @@ public ObjectResponse initVerificationSdk(
*/
@Transactional
public ResponseEntity initPresenceCheck(ObjectRequest request,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication apiAuthentication)
throws IdentityVerificationException, PowerAuthAuthenticationException, PowerAuthEncryptionException, OnboardingProcessException {
final String operationDescription = "initializing presence check";
checkApiAuthentication(apiAuthentication, operationDescription);
- checkEciesContext(eciesContext, operationDescription);
+ checkEncryptionContext(encryptionContext, operationDescription);
checkRequestObject(request, operationDescription);
final OwnerId ownerId = PowerAuthUtil.getOwnerId(apiAuthentication);
@@ -421,21 +423,21 @@ public ResponseEntity resendOtp(
/**
* Verify an OTP code received from the user.
* @param request Presence check initialization request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Send OTP response.
* @throws PowerAuthEncryptionException Thrown when request decryption fails.
* @throws OnboardingProcessException Thrown when onboarding process is not found.
*/
@Transactional
public ObjectResponse verifyOtp(ObjectRequest request,
- EciesEncryptionContext eciesContext)
+ EncryptionContext encryptionContext)
throws PowerAuthEncryptionException, OnboardingProcessException {
- checkEciesContext(eciesContext, "verifying OTP during identity verification");
+ checkEncryptionContext(encryptionContext, "verifying OTP during identity verification");
checkRequestObject(request, "verifying OTP during identity verification");
// Extract user ID from onboarding process for current activation
- final OwnerId ownerId = extractOwnerId(eciesContext);
+ final OwnerId ownerId = extractOwnerId(encryptionContext);
final String processId = request.getRequestObject().getProcessId();
logger.debug("Onboarding process will be locked using PESSIMISTIC_WRITE lock, {}", processId);
@@ -565,12 +567,12 @@ private void checkApiAuthentication(@Nullable PowerAuthApiAuthentication apiAuth
/**
* Checks if the request was correctly decrypted
- * @param eciesContext ECIES encryption context
+ * @param encryptionContext ECIES encryption context
* @param description Additional description
* @throws PowerAuthEncryptionException When the ECIES encryption context does not exist
*/
- private void checkEciesContext(@Nullable EciesEncryptionContext eciesContext, String description) throws PowerAuthEncryptionException {
- if (eciesContext == null) {
+ private void checkEncryptionContext(@Nullable EncryptionContext encryptionContext, String description) throws PowerAuthEncryptionException {
+ if (encryptionContext == null) {
throw new PowerAuthEncryptionException("ECIES encryption failed when " + description);
}
}
@@ -588,13 +590,13 @@ private void checkRequestObject(@Nullable ObjectRequest> request, String descr
}
/**
- * Extract owner identification from an ECIES context. The onboarding process is not locked.
+ * Extract owner identification from an Encryption context. The onboarding process is not locked.
*
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Owner identification.
*/
- private OwnerId extractOwnerId(EciesEncryptionContext eciesContext) throws OnboardingProcessException {
- return extractOwnerId(eciesContext.getActivationId());
+ private OwnerId extractOwnerId(EncryptionContext encryptionContext) throws OnboardingProcessException {
+ return extractOwnerId(encryptionContext.getActivationId());
}
/**
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/OnboardingServiceImpl.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/OnboardingServiceImpl.java
index 0e99d11a..e252958c 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/OnboardingServiceImpl.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/OnboardingServiceImpl.java
@@ -27,6 +27,7 @@
import com.wultra.app.enrollmentserver.api.model.onboarding.response.OnboardingConsentTextResponse;
import com.wultra.app.enrollmentserver.api.model.onboarding.response.OnboardingStartResponse;
import com.wultra.app.enrollmentserver.api.model.onboarding.response.OnboardingStatusResponse;
+import com.wultra.app.enrollmentserver.api.model.onboarding.response.data.ConfigurationDataDto;
import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
import com.wultra.app.enrollmentserver.model.enumeration.OtpType;
@@ -84,6 +85,11 @@ public class OnboardingServiceImpl extends CommonOnboardingService {
private final ActivationService activationService;
+ /**
+ * Configuration data for client integration
+ */
+ private final ConfigurationDataDto integrationConfigDto;
+
// Special instance of ObjectMapper for normalized serialization of identification data
private final ObjectMapper normalizedMapper = JsonMapper
.builder()
@@ -121,6 +127,9 @@ public OnboardingServiceImpl(
this.otpService = otpService;
this.activationService = activationService;
this.onboardingProvider = onboardingProvider;
+ this.integrationConfigDto = ConfigurationDataDto.builder()
+ .otpResendPeriod(onboardingConfig.getOtpResendPeriod().toString())
+ .otpResendPeriodSeconds(onboardingConfig.getOtpResendPeriod().toSeconds()).build();
}
/**
@@ -175,6 +184,7 @@ public OnboardingStartResponse startOnboarding(
OnboardingStartResponse response = new OnboardingStartResponse();
response.setProcessId(process.getId());
response.setOnboardingStatus(process.getStatus());
+ response.setConfig(integrationConfigDto);
return response;
}
@@ -223,6 +233,7 @@ public OnboardingStatusResponse getStatus(OnboardingStatusRequest request) throw
}
response.setOnboardingStatus(process.getStatus());
+ response.setConfig(integrationConfigDto);
return response;
}
@@ -358,8 +369,14 @@ public void approveConsent(final OnboardingConsentApprovalRequest request) throw
try {
final ApproveConsentResponse response = onboardingProvider.approveConsent(providerRequest);
- auditService.auditOnboardingProvider(process, "Approve consent text for user: {}", userId);
logger.debug("Got {} for processId={}", response, request.getProcessId());
+ if (response.isErrorOccurred()) {
+ final String errorDetail = response.getErrorDetail();
+ auditService.auditOnboardingProvider(process, "Consent text approval failed for user: {}, error: {}", userId, errorDetail);
+ throw new OnboardingProcessException("Consent text approval failed for process: %s, user: %s, error: %s"
+ .formatted(process.getId(), userId, errorDetail));
+ }
+ auditService.auditOnboardingProvider(process, "Approve consent text for user: {}", userId);
} catch (OnboardingProviderException e) {
throw new OnboardingProcessException("An error when approving consent.", e);
}
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckLimitService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckLimitService.java
index 997cca1d..241340b5 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckLimitService.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/PresenceCheckLimitService.java
@@ -22,11 +22,11 @@
import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase;
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
-import com.wultra.app.enrollmentserver.model.enumeration.OtpType;
import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.onboardingserver.common.database.IdentityVerificationRepository;
import com.wultra.app.onboardingserver.common.database.OnboardingOtpRepository;
import com.wultra.app.onboardingserver.common.database.OnboardingProcessRepository;
+import com.wultra.app.onboardingserver.common.database.ScaResultRepository;
import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.OnboardingProcessEntity;
import com.wultra.app.onboardingserver.common.errorhandling.IdentityVerificationException;
@@ -35,9 +35,8 @@
import com.wultra.app.onboardingserver.common.service.AuditService;
import com.wultra.app.onboardingserver.configuration.IdentityVerificationConfig;
import com.wultra.app.onboardingserver.errorhandling.PresenceCheckLimitException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import static com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus.FAILED;
@@ -48,14 +47,15 @@
* @author Roman Strobl, roman.strobl@wultra.com
*/
@Service
+@Slf4j
+@AllArgsConstructor
public class PresenceCheckLimitService {
- private static final Logger logger = LoggerFactory.getLogger(PresenceCheckLimitService.class);
-
private final IdentityVerificationConfig identityVerificationConfig;
private final OnboardingOtpRepository otpRepository;
private final IdentityVerificationRepository identityVerificationRepository;
private final OnboardingProcessRepository onboardingProcessRepository;
+ private final ScaResultRepository scaResultRepository;
private final ActivationFlagService activationFlagService;
private final IdentityVerificationService identityVerificationService;
@@ -63,35 +63,7 @@ public class PresenceCheckLimitService {
private final AuditService auditService;
/**
- * Service constructor.
- * @param identityVerificationConfig Identity verification configuration.
- * @param otpRepository Onboarding OTP repository.
- * @param identityVerificationRepository Identity verification repository.
- * @param onboardingProcessRepository Onboarding process repository.
- * @param activationFlagService Activation flag service.
- * @param identityVerificationService Identity verification service.
- */
- @Autowired
- public PresenceCheckLimitService(
- final IdentityVerificationConfig identityVerificationConfig,
- final OnboardingOtpRepository otpRepository,
- final IdentityVerificationRepository identityVerificationRepository,
- final OnboardingProcessRepository onboardingProcessRepository,
- final ActivationFlagService activationFlagService,
- final IdentityVerificationService identityVerificationService,
- final AuditService auditService) {
-
- this.identityVerificationConfig = identityVerificationConfig;
- this.otpRepository = otpRepository;
- this.identityVerificationRepository = identityVerificationRepository;
- this.onboardingProcessRepository = onboardingProcessRepository;
- this.activationFlagService = activationFlagService;
- this.identityVerificationService = identityVerificationService;
- this.auditService = auditService;
- }
-
- /**
- * Check limit for maximum number of attempts for presence check and OTP verification.
+ * Check limit for maximum number of attempts for SCA (presence check and OTP verification).
* @param ownerId Owner identification.
* @param processId Process identifier.
* @throws IdentityVerificationException Thrown when identity verification is invalid.
@@ -99,12 +71,15 @@ public PresenceCheckLimitService(
* @throws RemoteCommunicationException Thrown when communication with PowerAuth server fails.
*/
public void checkPresenceCheckMaxAttemptLimit(OwnerId ownerId, String processId) throws IdentityVerificationException, PresenceCheckLimitException, RemoteCommunicationException {
- final int otpCount = otpRepository.countByProcessIdAndType(processId, OtpType.USER_VERIFICATION);
- // TODO (racansky, 2022-10-20, #453) OTP verification could be turned off, logic should be based on another data
- if (otpCount >= identityVerificationConfig.getPresenceCheckMaxFailedAttempts()) {
- final IdentityVerificationEntity identityVerification = identityVerificationRepository.findFirstByActivationIdOrderByTimestampCreatedDesc(ownerId.getActivationId())
- .orElseThrow(() ->
- new IdentityVerificationException("Identity verification was not found, " + ownerId));
+ final IdentityVerificationEntity identityVerification = identityVerificationRepository.findFirstByActivationIdOrderByTimestampCreatedDesc(ownerId.getActivationId())
+ .orElseThrow(() ->
+ new IdentityVerificationException("Identity verification was not found, " + ownerId));
+
+ final int count = scaResultRepository.countByIdentityVerification(identityVerification);
+ logger.debug("SCA attempts count so far {}, {}", count, ownerId);
+
+ if (count >= identityVerificationConfig.getPresenceCheckMaxFailedAttempts()) {
+
if (!identityVerification.getProcessId().equals(processId)) {
throw new IdentityVerificationException(String.format("Process identifier mismatch for owner %s: %s", ownerId, processId));
}
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 33ce46f3..f72931cf 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
@@ -22,8 +22,10 @@
import com.wultra.app.enrollmentserver.model.enumeration.*;
import com.wultra.app.enrollmentserver.model.integration.*;
import com.wultra.app.onboardingserver.common.database.DocumentVerificationRepository;
+import com.wultra.app.onboardingserver.common.database.ScaResultRepository;
import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
+import com.wultra.app.onboardingserver.common.database.entity.ScaResultEntity;
import com.wultra.app.onboardingserver.common.errorhandling.IdentityVerificationException;
import com.wultra.app.onboardingserver.common.errorhandling.OnboardingProcessLimitException;
import com.wultra.app.onboardingserver.common.errorhandling.RemoteCommunicationException;
@@ -35,14 +37,15 @@
import com.wultra.app.onboardingserver.impl.service.document.DocumentProcessingService;
import com.wultra.app.onboardingserver.impl.service.internal.JsonSerializationService;
import com.wultra.app.onboardingserver.provider.PresenceCheckProvider;
+import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
+import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -56,6 +59,7 @@
* @author Lukas Lukovsky, lukas.lukovsky@wultra.com
*/
@Service
+@AllArgsConstructor
public class PresenceCheckService {
private static final Logger logger = LoggerFactory.getLogger(PresenceCheckService.class);
@@ -72,40 +76,7 @@ public class PresenceCheckService {
private final PresenceCheckLimitService presenceCheckLimitService;
private final AuditService auditService;
private final ImageProcessor imageProcessor;
-
- /**
- * Service constructor.
- * @param documentVerificationRepository Document verification repository.
- * @param documentProcessingService Document processing service.
- * @param identityVerificationService Identity verification service.
- * @param jsonSerializationService JSON serialization service.
- * @param presenceCheckProvider Presence check provider.
- * @param presenceCheckLimitService Presence check limit service.
- * @param auditService Audit service.
- * @param imageProcessor Image processor.
- */
- @Autowired
- public PresenceCheckService(
- final IdentityVerificationConfig identityVerificationConfig,
- final DocumentVerificationRepository documentVerificationRepository,
- final DocumentProcessingService documentProcessingService,
- final IdentityVerificationService identityVerificationService,
- final JsonSerializationService jsonSerializationService,
- final PresenceCheckProvider presenceCheckProvider,
- final PresenceCheckLimitService presenceCheckLimitService,
- final AuditService auditService,
- final ImageProcessor imageProcessor) {
-
- this.identityVerificationConfig = identityVerificationConfig;
- this.documentVerificationRepository = documentVerificationRepository;
- this.documentProcessingService = documentProcessingService;
- this.identityVerificationService = identityVerificationService;
- this.jsonSerializationService = jsonSerializationService;
- this.presenceCheckProvider = presenceCheckProvider;
- this.presenceCheckLimitService = presenceCheckLimitService;
- this.auditService = auditService;
- this.imageProcessor = imageProcessor;
- }
+ private final ScaResultRepository scaResultRepository;
/**
* Prepares presence check to not initialized state.
@@ -163,15 +134,17 @@ public void checkPresenceVerification(
final PresenceCheckResult result = presenceCheckProvider.getResult(ownerId, sessionInfo);
auditService.auditPresenceCheckProvider(idVerification, "Got presence check result: {} for user: {}", result.getStatus(), ownerId.getUserId());
+ // Evaluate the result
if (result.getStatus() != PresenceCheckStatus.ACCEPTED) {
logger.info("Not accepted presence check, status: {}, {}", result.getStatus(), ownerId);
- evaluatePresenceCheckResult(ownerId, idVerification, result);
- return;
+ } else {
+ logger.debug("Processing a result of an accepted presence check, {}", ownerId);
}
- logger.debug("Processing a result of an accepted presence check, {}", ownerId);
+ // Process the photo irrespective of the result status
final Image photo = result.getPhoto();
if (photo == null) {
+ evaluatePresenceCheckResult(ownerId, idVerification, result);
throw new PresenceCheckException("Missing person photo from presence verification, " + ownerId);
}
logger.debug("Obtained a photo from the result, {}", ownerId);
@@ -311,15 +284,18 @@ private void evaluatePresenceCheckResult(OwnerId ownerId,
final IdentityVerificationPhase phase = idVerification.getPhase();
switch (result.getStatus()) {
- case ACCEPTED ->
+ case ACCEPTED -> {
// The timestampFinished parameter is not set yet, there may be other steps ahead
- identityVerificationService.moveToPhaseAndStatus(idVerification, phase, ACCEPTED, ownerId);
+ saveScaResult(ScaResultEntity.Result.SUCCESS, idVerification, ownerId);
+ identityVerificationService.moveToPhaseAndStatus(idVerification, phase, ACCEPTED, ownerId);
+ }
case FAILED -> {
idVerification.setErrorDetail(IdentityVerificationEntity.PRESENCE_CHECK_REJECTED);
idVerification.setErrorOrigin(ErrorOrigin.PRESENCE_CHECK);
idVerification.setTimestampFailed(ownerId.getTimestamp());
- identityVerificationService.moveToPhaseAndStatus(idVerification, phase, FAILED, ownerId);
logger.warn("Presence check failed, {}, errorDetail: '{}'", ownerId, result.getErrorDetail());
+ saveScaResult(ScaResultEntity.Result.FAILED, idVerification, ownerId);
+ identityVerificationService.moveToPhaseAndStatus(idVerification, phase, FAILED, ownerId);
}
case IN_PROGRESS ->
logger.debug("Presence check still in progress, {}", ownerId);
@@ -328,6 +304,7 @@ private void evaluatePresenceCheckResult(OwnerId ownerId,
idVerification.setRejectOrigin(RejectOrigin.PRESENCE_CHECK);
idVerification.setTimestampFinished(ownerId.getTimestamp());
logger.info("Presence check rejected, {}, rejectReason: '{}'", ownerId, result.getRejectReason());
+ saveScaResult(ScaResultEntity.Result.FAILED, idVerification, ownerId);
identityVerificationService.moveToPhaseAndStatus(idVerification, phase, REJECTED, ownerId);
}
default ->
@@ -336,6 +313,16 @@ private void evaluatePresenceCheckResult(OwnerId ownerId,
}
}
+ private void saveScaResult(final ScaResultEntity.Result presenceCheckResult, final IdentityVerificationEntity identityVerification, final OwnerId ownerId) {
+ logger.debug("Saving SCA presence check result: {}, identity verification ID: {}, {}", presenceCheckResult, identityVerification.getId(), ownerId);
+ final ScaResultEntity scaResultEntity = new ScaResultEntity();
+ scaResultEntity.setPresenceCheckResult(presenceCheckResult);
+ scaResultEntity.setIdentityVerification(identityVerification);
+ scaResultEntity.setProcessId(identityVerification.getProcessId());
+ scaResultEntity.setTimestampCreated(new Date());
+ scaResultRepository.save(scaResultEntity);
+ }
+
/**
* Fetches a current identity verification for presence check initialization
* @param ownerId Owner identification.
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 2710de77..4d0e2b73 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
@@ -410,14 +410,14 @@ private void processDocsSubmitResults(OwnerId ownerId, DocumentVerificationEntit
docVerification.setErrorOrigin(ErrorOrigin.DOCUMENT_VERIFICATION);
logger.info("Document verification ID: {}, failed: {}, {}",
docVerification.getId(), docSubmitResult.getErrorDetail(), ownerId);
- auditService.audit(docVerification, "Document verification failed for user: {}", ownerId.getUserId());
+ auditService.audit(docVerification, "Document verification failed for user: {}, detail: {}", ownerId.getUserId(), docSubmitResult.getErrorDetail());
} else if (StringUtils.isNotBlank(docSubmitResult.getRejectReason())) {
docVerification.setStatus(DocumentStatus.REJECTED);
docVerification.setRejectReason(ErrorDetail.DOCUMENT_VERIFICATION_REJECTED);
docVerification.setRejectOrigin(RejectOrigin.DOCUMENT_VERIFICATION);
logger.info("Document verification ID: {}, rejected: {}, {}",
- docVerification.getId(), docSubmitResult.getErrorDetail(), ownerId);
- auditService.audit(docVerification, "Document verification rejected for user: {}", ownerId.getUserId());
+ docVerification.getId(), docSubmitResult.getRejectReason(), ownerId);
+ auditService.audit(docVerification, "Document verification rejected for user: {}, reason: {}", ownerId.getUserId(), docSubmitResult.getRejectReason());
} else {
docVerification.setPhotoId(docsSubmitResults.getExtractedPhotoId());
docVerification.setProviderName(identityVerificationConfig.getDocumentVerificationProvider());
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/presencecheck/iproov/config/IProovConfig.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/presencecheck/iproov/config/IProovConfig.java
index b0c96b91..7f565556 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/presencecheck/iproov/config/IProovConfig.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/presencecheck/iproov/config/IProovConfig.java
@@ -28,6 +28,7 @@
import io.netty.channel.ChannelOption;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@@ -55,6 +56,7 @@
import org.springframework.web.util.UriComponentsBuilder;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
+import reactor.netty.transport.ProxyProvider;
import java.time.Duration;
import java.util.Map;
@@ -170,7 +172,7 @@ private static ReactiveOAuth2AccessTokenResponseClient {
+ final ProxyProvider.Builder proxyBuilder = proxySpec
+ .type(ProxyProvider.Proxy.HTTP)
+ .host(restClientConfig.getProxyHost())
+ .port(restClientConfig.getProxyPort());
+ if (StringUtils.isNotBlank(restClientConfig.getProxyUsername())) {
+ proxyBuilder.username(restClientConfig.getProxyUsername());
+ proxyBuilder.password(s -> restClientConfig.getProxyPassword());
+ }
+
+ proxyBuilder.build();
+ });
+ }
+
final ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
return WebClient.builder()
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ApproveConsentResponse.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ApproveConsentResponse.java
index 052f52e4..80df2d13 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ApproveConsentResponse.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ApproveConsentResponse.java
@@ -21,6 +21,7 @@
import com.wultra.app.onboardingserver.provider.OnboardingProvider;
import com.wultra.app.onboardingserver.provider.model.request.ApproveConsentRequest;
import com.wultra.core.annotations.PublicApi;
+import lombok.Getter;
import lombok.ToString;
/**
@@ -30,6 +31,16 @@
*/
@PublicApi
@ToString
+@Getter
public final class ApproveConsentResponse {
- // empty so far, open to change in the future
+
+ /**
+ * Whether business logic error occurred during consent approval.
+ */
+ private boolean errorOccurred;
+
+ /**
+ * Error detail to store within onboarding process.
+ */
+ private String errorDetail;
}
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ProcessEventResponse.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ProcessEventResponse.java
index 538948f7..3abce44f 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ProcessEventResponse.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/response/ProcessEventResponse.java
@@ -35,5 +35,14 @@
@ToString
@PublicApi
public final class ProcessEventResponse {
- // empty so far
+
+ /**
+ * Whether business logic error occurred during event processing.
+ */
+ private boolean errorOccurred;
+
+ /**
+ * Error detail to store within onboarding process.
+ */
+ private String errorDetail;
}
diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProviderAutoConfiguration.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProviderAutoConfiguration.java
index d2c12107..fc283866 100644
--- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProviderAutoConfiguration.java
+++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProviderAutoConfiguration.java
@@ -65,7 +65,7 @@ public RestClient restClient(
final RestClientConfiguration config = new RestClientConfiguration();
config.setBaseUrl(url);
- config.setConnectionTimeout((int) configuration.getConnectionTimeout().toMillis());
+ config.setConnectionTimeout(configuration.getConnectionTimeout());
config.setResponseTimeout(configuration.getResponseTimeout());
config.setHandshakeTimeout(configuration.getHandshakeTimeout());
config.setDefaultHttpHeaders(headers);
diff --git a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheckTest.java b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheckTest.java
index 9b56f494..89cfb676 100644
--- a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheckTest.java
+++ b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/IdentityVerificationPrecompleteCheckTest.java
@@ -22,9 +22,11 @@
import com.wultra.app.enrollmentserver.model.enumeration.OtpType;
import com.wultra.app.onboardingserver.common.database.DocumentVerificationRepository;
import com.wultra.app.onboardingserver.common.database.OnboardingOtpRepository;
+import com.wultra.app.onboardingserver.common.database.ScaResultRepository;
import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.OnboardingOtpEntity;
+import com.wultra.app.onboardingserver.common.database.entity.ScaResultEntity;
import com.wultra.app.onboardingserver.configuration.IdentityVerificationConfig;
import com.wultra.app.onboardingserver.statemachine.guard.document.RequiredDocumentTypesCheck;
import com.wultra.security.powerauth.client.model.enumeration.ActivationStatus;
@@ -66,11 +68,23 @@ class IdentityVerificationPrecompleteCheckTest {
@Mock
private ActivationService activationService;
+ @Mock
+ private ScaResultRepository scaResultRepository;
+
@InjectMocks
private IdentityVerificationPrecompleteCheck tested;
@Test
void testProcessDocumentVerificationResult_valid() throws Exception {
+ final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
+ idVerification.setProcessId("process-1");
+ idVerification.setActivationId("activation-1");
+ idVerification.setPhase(OTP_VERIFICATION);
+ idVerification.setStatus(VERIFICATION_PENDING);
+
+ final ScaResultEntity scaResult = new ScaResultEntity();
+ scaResult.setScaResult(ScaResultEntity.Result.SUCCESS);
+
when(requiredDocumentTypesCheck.evaluate(any(), any()))
.thenReturn(true);
when(identityVerificationConfig.isVerificationOtpEnabled())
@@ -82,12 +96,8 @@ void testProcessDocumentVerificationResult_valid() throws Exception {
.thenReturn(Optional.of(createOtp()));
when(activationService.fetchActivationStatus("activation-1"))
.thenReturn(ActivationStatus.ACTIVE);
-
- final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
- idVerification.setProcessId("process-1");
- idVerification.setActivationId("activation-1");
- idVerification.setPhase(OTP_VERIFICATION);
- idVerification.setStatus(VERIFICATION_PENDING);
+ when(scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(idVerification))
+ .thenReturn(Optional.of(scaResult));
final var result = tested.evaluate(idVerification);
@@ -137,6 +147,15 @@ void testProcessDocumentVerificationResult_invalidActivationOtp() throws Excepti
@Test
void testProcessDocumentVerificationResult_validStateWithoutOtp() throws Exception {
+ final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
+ idVerification.setProcessId("process-1");
+ idVerification.setPhase(PRESENCE_CHECK);
+ idVerification.setStatus(ACCEPTED);
+ idVerification.setActivationId("activation-1");
+
+ final ScaResultEntity scaResult = new ScaResultEntity();
+ scaResult.setScaResult(ScaResultEntity.Result.SUCCESS);
+
when(requiredDocumentTypesCheck.evaluate(any(), any()))
.thenReturn(true);
when(activationService.fetchActivationStatus("activation-1"))
@@ -144,11 +163,8 @@ void testProcessDocumentVerificationResult_validStateWithoutOtp() throws Excepti
when(onboardingOtpRepository.findNewestByProcessIdAndType("process-1", OtpType.ACTIVATION))
.thenReturn(Optional.of(createOtp()));
- final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
- idVerification.setProcessId("process-1");
- idVerification.setPhase(PRESENCE_CHECK);
- idVerification.setStatus(ACCEPTED);
- idVerification.setActivationId("activation-1");
+ when(scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(idVerification))
+ .thenReturn(Optional.of(scaResult));
final var result = tested.evaluate(idVerification);
@@ -157,18 +173,23 @@ void testProcessDocumentVerificationResult_validStateWithoutOtp() throws Excepti
@Test
void testProcessDocumentVerificationResult_validStateWithoutOtpAndPresenceCheck() throws Exception {
+ final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
+ idVerification.setProcessId("process-1");
+ idVerification.setActivationId("activation-1");
+ idVerification.setPhase(CLIENT_EVALUATION);
+ idVerification.setStatus(ACCEPTED);
+
+ final ScaResultEntity scaResult = new ScaResultEntity();
+ scaResult.setScaResult(ScaResultEntity.Result.SUCCESS);
+
when(requiredDocumentTypesCheck.evaluate(any(), any()))
.thenReturn(true);
when(activationService.fetchActivationStatus("activation-1"))
.thenReturn(ActivationStatus.ACTIVE);
when(onboardingOtpRepository.findNewestByProcessIdAndType("process-1", OtpType.ACTIVATION))
.thenReturn(Optional.of(createOtp()));
-
- final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
- idVerification.setProcessId("process-1");
- idVerification.setActivationId("activation-1");
- idVerification.setPhase(CLIENT_EVALUATION);
- idVerification.setStatus(ACCEPTED);
+ when(scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(idVerification))
+ .thenReturn(Optional.of(scaResult));
final var result = tested.evaluate(idVerification);
@@ -246,6 +267,36 @@ void testProcessDocumentVerificationResult_invalidStatus() throws Exception {
assertEquals("Not valid phase and state", result.getErrorDetail());
}
+ @Test
+ void testProcessDocumentVerificationResult_invalidSca() throws Exception {
+ final IdentityVerificationEntity idVerification = new IdentityVerificationEntity();
+ idVerification.setProcessId("process-1");
+ idVerification.setActivationId("activation-1");
+ idVerification.setPhase(OTP_VERIFICATION);
+ idVerification.setStatus(VERIFICATION_PENDING);
+
+ final ScaResultEntity scaResult = new ScaResultEntity();
+ scaResult.setScaResult(ScaResultEntity.Result.FAILED);
+
+ when(requiredDocumentTypesCheck.evaluate(any(), any()))
+ .thenReturn(true);
+ when(identityVerificationConfig.isVerificationOtpEnabled())
+ .thenReturn(true);
+
+ when(onboardingOtpRepository.findNewestByProcessIdAndType("process-1", OtpType.USER_VERIFICATION))
+ .thenReturn(Optional.of(createOtp()));
+ when(onboardingOtpRepository.findNewestByProcessIdAndType("process-1", OtpType.ACTIVATION))
+ .thenReturn(Optional.of(createOtp()));
+ when(activationService.fetchActivationStatus("activation-1"))
+ .thenReturn(ActivationStatus.ACTIVE);
+ when(scaResultRepository.findTopByIdentityVerificationOrderByTimestampCreatedDesc(idVerification))
+ .thenReturn(Optional.of(scaResult));
+
+ final var result = tested.evaluate(idVerification);
+
+ assertFalse(result.isSuccessful());
+ }
+
private static OnboardingOtpEntity createOtp() {
final OnboardingOtpEntity otp = new OnboardingOtpEntity();
otp.setStatus(OtpStatus.VERIFIED);
diff --git a/enrollment-server/pom.xml b/enrollment-server/pom.xml
index d711c9bb..7df6e872 100644
--- a/enrollment-server/pom.xml
+++ b/enrollment-server/pom.xml
@@ -142,12 +142,6 @@
test
-
- org.apache.tomcat.embed
- tomcat-embed-el
- test
-
-
org.springdoc
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java
index 4c6a41b0..9c12aeec 100644
--- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java
@@ -24,13 +24,13 @@
import com.wultra.app.enrollmentserver.impl.service.ActivationCodeService;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
-import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope;
import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureTypes;
import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuth;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption;
import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication;
-import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthAuthenticationException;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException;
import io.swagger.v3.oas.annotations.Parameter;
@@ -73,7 +73,7 @@ public ActivationCodeController(ActivationCodeService activationCodeService) {
* Controller request handler for requesting the activation code.
*
* @param request Request with activation OTP.
- * @param eciesContext ECIES encryption context.
+ * @param encryptionContext ECIES encryption context.
* @param apiAuthentication Authentication object with user and app details.
* @return New activation code, activation code signature and activation ID.
* @throws PowerAuthAuthenticationException In case user authentication fails.
@@ -82,13 +82,13 @@ public ActivationCodeController(ActivationCodeService activationCodeService) {
* @throws ActivationCodeException In case fetching the activation code fails.
*/
@PostMapping("code")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
@PowerAuth(resourceId = "/api/activation/code", signatureType = {
PowerAuthSignatureTypes.POSSESSION_BIOMETRY,
PowerAuthSignatureTypes.POSSESSION_KNOWLEDGE
})
public ObjectResponse requestActivationCode(@EncryptedRequestBody ObjectRequest request,
- @Parameter(hidden = true) EciesEncryptionContext eciesContext,
+ @Parameter(hidden = true) EncryptionContext encryptionContext,
@Parameter(hidden = true) PowerAuthApiAuthentication apiAuthentication) throws PowerAuthAuthenticationException, InvalidRequestObjectException, ActivationCodeException, PowerAuthEncryptionException {
// Check if the authentication object is present
if (apiAuthentication == null) {
@@ -97,7 +97,7 @@ public ObjectResponse requestActivationCode(@EncryptedRe
}
// Check if the request was correctly decrypted
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("ECIES encryption failed when fetching activation code");
throw new PowerAuthEncryptionException("ECIES decryption failed when fetching activation code");
}
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 518efff3..35358964 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
@@ -205,7 +205,7 @@ public Response operationApprove(
.signatureFactors(signatureFactors)
.requestContext(requestContext)
.activationFlags(activationFlags)
- .proximityCheckOtp(requestObject.getProximityCheckOtp())
+ .proximityCheckOtp(fetchProximityCheckOtp(requestObject))
.build();
return mobileTokenService.operationApprove(serviceRequest);
@@ -221,6 +221,15 @@ public Response operationApprove(
}
}
+ private static String fetchProximityCheckOtp(OperationApproveRequest requestObject) {
+ if (requestObject.getProximityCheck().isEmpty()) {
+ return null;
+ }
+ final var proximityCheck = requestObject.getProximityCheck().get();
+ logger.info("Operation ID: {} using proximity check OTP, timestampRequested: {}, timestampSigned: {}", requestObject.getId(), proximityCheck.getTimestampRequested(), proximityCheck.getTimestampSigned());
+ return proximityCheck.getOtp();
+ }
+
/**
* Operation reject.
*
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/mock/controller/v3/EncryptedDataExchangeController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/mock/controller/v3/EncryptedDataExchangeController.java
index 03322c2b..9f827825 100644
--- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/mock/controller/v3/EncryptedDataExchangeController.java
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/mock/controller/v3/EncryptedDataExchangeController.java
@@ -23,17 +23,16 @@
import com.wultra.app.enrollmentserver.mock.model.response.DataExchangeResponse;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
-import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope;
import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuth;
import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption;
import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication;
-import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext;
+import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthAuthenticationException;
import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException;
import io.getlime.security.powerauth.rest.api.spring.exception.authentication.PowerAuthSignatureInvalidException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@@ -50,59 +49,58 @@
*/
@RestController("encryptedDataExchangeControllerV3")
@RequestMapping(value = "/exchange")
+@Slf4j
public class EncryptedDataExchangeController {
- private final static Logger logger = LoggerFactory.getLogger(EncryptedDataExchangeController.class);
-
/**
* Sample encrypted data exchange in application scope.
*
* @param request Data exchange request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Data exchange response.
* @throws PowerAuthEncryptionException In case encryption or decryption fails.
*/
@RequestMapping(value = "v3/application", method = RequestMethod.POST)
- @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE)
public DataExchangeResponse exchangeInApplicationScope(@EncryptedRequestBody DataExchangeRequest request,
- EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException {
+ EncryptionContext encryptionContext) throws PowerAuthEncryptionException {
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
// Return a slightly different String containing original data in response
- return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + eciesContext.getEciesScope());
+ return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + encryptionContext.getEncryptionScope());
}
/**
* Sample encrypted data exchange in activation scope.
*
* @param request Data exchange request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @return Data exchange response.
* @throws PowerAuthEncryptionException In case encryption or decryption fails.
*/
@RequestMapping(value = "v3/activation", method = RequestMethod.POST)
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
public DataExchangeResponse exchangeInActivationScope(@EncryptedRequestBody DataExchangeRequest request,
- EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException {
+ EncryptionContext encryptionContext) throws PowerAuthEncryptionException {
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
// Return a slightly different String containing original data in response
- return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + eciesContext.getEciesScope());
+ return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + encryptionContext.getEncryptionScope());
}
/**
* Sample signed and encrypted data exchange.
*
* @param request Data exchange request.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param auth PowerAuth authentication object.
* @return Data exchange response.
* @throws PowerAuthAuthenticationException In case signature validation fails.
@@ -110,9 +108,9 @@ public DataExchangeResponse exchangeInActivationScope(@EncryptedRequestBody Data
*/
@RequestMapping(value = "v3/signed", method = RequestMethod.POST)
@PowerAuth(resourceId = "/exchange/v3/signed")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
public DataExchangeResponse exchangeSignedAndEncryptedData(@EncryptedRequestBody DataExchangeRequest request,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication auth) throws PowerAuthAuthenticationException, PowerAuthEncryptionException {
if (auth == null || auth.getUserId() == null) {
@@ -120,7 +118,7 @@ public DataExchangeResponse exchangeSignedAndEncryptedData(@EncryptedRequestBody
throw new PowerAuthSignatureInvalidException();
}
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
@@ -133,7 +131,7 @@ public DataExchangeResponse exchangeSignedAndEncryptedData(@EncryptedRequestBody
* Sample signed and encrypted data exchange of String data.
*
* @param requestData Request with String data.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param auth PowerAuth authentication object.
* @return Data exchange response.
* @throws PowerAuthAuthenticationException In case signature validation fails.
@@ -141,9 +139,9 @@ public DataExchangeResponse exchangeSignedAndEncryptedData(@EncryptedRequestBody
*/
@RequestMapping(value = "v3/signed/string", method = RequestMethod.POST)
@PowerAuth(resourceId = "/exchange/v3/signed/string")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
public String exchangeSignedAndEncryptedDataString(@EncryptedRequestBody String requestData,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication auth) throws PowerAuthAuthenticationException, PowerAuthEncryptionException {
if (auth == null || auth.getUserId() == null) {
@@ -151,7 +149,7 @@ public String exchangeSignedAndEncryptedDataString(@EncryptedRequestBody String
throw new PowerAuthSignatureInvalidException();
}
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
@@ -164,7 +162,7 @@ public String exchangeSignedAndEncryptedDataString(@EncryptedRequestBody String
* Sample signed and encrypted data exchange of raw data as byte[].
*
* @param requestData Request with raw byte[] data.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param auth PowerAuth authentication object.
* @return Data exchange response.
* @throws PowerAuthAuthenticationException In case signature validation fails.
@@ -172,9 +170,9 @@ public String exchangeSignedAndEncryptedDataString(@EncryptedRequestBody String
*/
@RequestMapping(value = "v3/signed/raw", method = RequestMethod.POST)
@PowerAuth(resourceId = "/exchange/v3/signed/raw")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
public byte[] exchangeSignedAndEncryptedDataRaw(@EncryptedRequestBody byte[] requestData,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication auth) throws PowerAuthAuthenticationException, PowerAuthEncryptionException {
if (auth == null || auth.getUserId() == null) {
@@ -182,7 +180,7 @@ public byte[] exchangeSignedAndEncryptedDataRaw(@EncryptedRequestBody byte[] req
throw new PowerAuthSignatureInvalidException();
}
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
@@ -195,7 +193,7 @@ public byte[] exchangeSignedAndEncryptedDataRaw(@EncryptedRequestBody byte[] req
* Sample signed and encrypted data exchange of generified request.
*
* @param request Request with generified request data.
- * @param eciesContext ECIES context.
+ * @param encryptionContext Encryption context.
* @param auth PowerAuth authentication object.
* @return Generified data exchange response.
* @throws PowerAuthAuthenticationException In case signature validation fails.
@@ -203,16 +201,16 @@ public byte[] exchangeSignedAndEncryptedDataRaw(@EncryptedRequestBody byte[] req
*/
@RequestMapping(value = "v3/signed/generics", method = RequestMethod.POST)
@PowerAuth(resourceId = "/exchange/v3/signed/generics")
- @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE)
public ObjectResponse exchangeSignedAndEncryptedDataGenerics(@EncryptedRequestBody ObjectRequest request,
- EciesEncryptionContext eciesContext,
+ EncryptionContext encryptionContext,
PowerAuthApiAuthentication auth) throws PowerAuthAuthenticationException, PowerAuthEncryptionException {
if (auth == null || auth.getUserId() == null) {
logger.info("Signature validation failed");
throw new PowerAuthSignatureInvalidException();
}
- if (eciesContext == null) {
+ if (encryptionContext == null) {
logger.error("Encryption failed");
throw new PowerAuthEncryptionException();
}
diff --git a/mtoken-model/pom.xml b/mtoken-model/pom.xml
index 6e2fa941..7b11a399 100644
--- a/mtoken-model/pom.xml
+++ b/mtoken-model/pom.xml
@@ -38,6 +38,11 @@
com.fasterxml.jackson.core
jackson-annotations
+
+
+ io.swagger.core.v3
+ swagger-annotations-jakarta
+
diff --git a/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/request/OperationApproveRequest.java b/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/request/OperationApproveRequest.java
index 699ac73d..08a2c686 100644
--- a/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/request/OperationApproveRequest.java
+++ b/mtoken-model/src/main/java/com/wultra/security/powerauth/lib/mtoken/model/request/OperationApproveRequest.java
@@ -18,9 +18,13 @@
package com.wultra.security.powerauth.lib.mtoken.model.request;
import com.wultra.security.powerauth.lib.mtoken.model.entity.PreApprovalScreen;
+import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
+import java.time.Instant;
+import java.util.Optional;
+
/**
* Request for online token signature verification.
*
@@ -35,7 +39,40 @@ public class OperationApproveRequest {
private String data;
/**
- * Optional OTP used for proximity check. User is instructed by {@link PreApprovalScreen.ScreenType#QR_SCAN}.
+ * Optional proximity check data. User is instructed by {@link PreApprovalScreen.ScreenType#QR_SCAN}.
*/
- private String proximityCheckOtp;
+ @Schema(description = "Optional proximity check data." )
+ private ProximityCheck proximityCheck;
+
+ public Optional getProximityCheck() {
+ return Optional.ofNullable(proximityCheck);
+ }
+
+ @Data
+ public static class ProximityCheck {
+
+ @NotNull
+ @Schema(description = "OTP used for proximity check.")
+ private String otp;
+
+ @Schema(description = "Source from where the OTP has been gained.")
+ private Type type;
+
+ /**
+ * When OTP obtained by the client. An optional hint for possible better estimation of the time shift correction.
+ */
+ @Schema(description = "When OTP requested by the client. An optional hint for possible better estimation of the time shift correction.")
+ private Instant timestampRequested;
+
+ /**
+ * When OTP signed by the client. An optional hint for possible better estimation of the time shift correction.
+ */
+ @Schema(description = "When OTP signed by the client. An optional hint for possible better estimation of the time shift correction.")
+ private Instant timestampSigned;
+
+ public enum Type {
+ QR_CODE,
+ DEEPLINK
+ }
+ }
}
diff --git a/pom.xml b/pom.xml
index 7335be9a..d4341aa0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.1.0
+ 3.1.3
@@ -87,19 +87,17 @@
6.5.0
5.1.0
- 3.2.0
+ 3.2.1
2.2.8
2.1.0
1.4.2
-
- 3.1.0
1.7.0-SNAPSHOT
1.5.0-SNAPSHOT
1.5.0-SNAPSHOT
1.5.0-SNAPSHOT
- 1.74
+ 1.76
@@ -182,6 +180,13 @@
${powerauth-push.version}
+
+ org.apache.tomcat.embed
+ tomcat-embed-el
+ ${tomcat.version}
+ test
+
+
org.javamoney.moneta
moneta-core