valuesetEntityOptional = valuesetRepository.findById(id);
+
+ ValuesetEntity valuesetEntity = valuesetEntityOptional.orElse(new ValuesetEntity());
+ valuesetEntity.setId(id);
+ valuesetEntity.setJson(value);
+
+ valuesetRepository.save(valuesetEntity);
+ }
+
+}
diff --git a/src/main/java/eu/europa/ec/dgc/gateway/utils/DgcMdc.java b/src/main/java/eu/europa/ec/dgc/gateway/utils/DgcMdc.java
new file mode 100644
index 00000000..db7312d7
--- /dev/null
+++ b/src/main/java/eu/europa/ec/dgc/gateway/utils/DgcMdc.java
@@ -0,0 +1,112 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.utils;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.slf4j.MDC;
+
+/**
+ * Wrapper for MDC to escape values for better log files.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DgcMdc {
+
+ /**
+ * Put a diagnostic context value (the value
parameter) as identified with the
+ * key
parameter into the current thread's diagnostic context map. The
+ * key
parameter cannot be null. The value
parameter
+ * can be null only if the underlying implementation supports it.
+ *
+ * This method delegates all work to the MDC of the underlying logging system.
+ *
+ * @param key non-null key
+ * @param value value to put in the map
+ * @throws IllegalArgumentException in case the "key" parameter is null
+ */
+ public static void put(String key, String value) {
+ value = value == null ? ">>null<<" : value;
+
+ value = value.replace("\"", "'");
+ MDC.put(key, "\"" + value + "\"");
+ }
+
+ /**
+ * Put a diagnostic context value (the value
parameter) as identified with the
+ * key
parameter into the current thread's diagnostic context map. The
+ * key
parameter cannot be null. The value
parameter
+ * can be null only if the underlying implementation supports it.
+ *
+ *
This method delegates all work to the MDC of the underlying logging system.
+ *
+ * @param key non-null key
+ * @param value a numeric value to put in the map
+ * @throws IllegalArgumentException in case the "key" parameter is null
+ */
+ public static void put(String key, long value) {
+ put(key, String.valueOf(value));
+ }
+
+ /**
+ * Put a diagnostic context value (the value
parameter) as identified with the
+ * key
parameter into the current thread's diagnostic context map. The
+ * key
parameter cannot be null. The value
parameter
+ * can be null only if the underlying implementation supports it.
+ *
+ *
This method delegates all work to the MDC of the underlying logging system.
+ *
+ * @param key non-null key
+ * @param value a date value to put in the map
+ * @throws IllegalArgumentException in case the "key" parameter is null
+ */
+ public static void put(String key, Date value) {
+ ZonedDateTime timestamp = ZonedDateTime.ofInstant(value.toInstant(), ZoneOffset.UTC);
+
+ put(
+ key,
+ timestamp.format(DateTimeFormatter.ISO_INSTANT)
+ );
+ }
+
+ /**
+ * Remove the diagnostic context identified by the key
parameter using
+ * the underlying system's MDC implementation. The key
parameter
+ * cannot be null. This method does nothing if there is no previous value
+ * associated with key
.
+ *
+ * @param key non-null key
+ * @throws IllegalArgumentException in case the "key" parameter is null
+ */
+ public static void remove(String key) {
+ MDC.remove(key);
+ }
+
+ /**
+ * Clear all entries in the MDC of the underlying implementation.
+ */
+ public static void clear() {
+ MDC.clear();
+ }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 00000000..d8c6d0f8
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,14 @@
+spring:
+ h2:
+ console:
+ enabled: true
+ path: /h2-console
+springdoc:
+ api-docs:
+ path: /api/docs
+ enabled: true
+ swagger-ui:
+ path: /swagger
+dgc:
+ trustAnchor:
+ keyStorePath: classpath:keystore/dgc-ta.jks
diff --git a/src/main/resources/application-h2.yml b/src/main/resources/application-h2.yml
new file mode 100644
index 00000000..a25b168f
--- /dev/null
+++ b/src/main/resources/application-h2.yml
@@ -0,0 +1,9 @@
+spring:
+ datasource:
+ jndi-name: false
+ driver-class-name: org.h2.Driver
+ url: jdbc:h2:mem:dgc;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;
+ username: sa
+ password: ''
+ jpa:
+ database-platform: org.hibernate.dialect.H2Dialect
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
new file mode 100644
index 00000000..d8a239f6
--- /dev/null
+++ b/src/main/resources/application-local.yml
@@ -0,0 +1,6 @@
+springdoc:
+ api-docs:
+ path: /api/docs
+ enabled: true
+ swagger-ui:
+ path: /swagger
diff --git a/src/main/resources/application-mysql.yml b/src/main/resources/application-mysql.yml
new file mode 100644
index 00000000..8fdafc86
--- /dev/null
+++ b/src/main/resources/application-mysql.yml
@@ -0,0 +1,9 @@
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/dgc
+ username: sa
+ password: sa
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ jndi-name: false
+ jpa:
+ database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 00000000..558d7801
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,48 @@
+server:
+ port: 8090
+spring:
+ profiles:
+ group:
+ "dev": "h2"
+ application:
+ name: eu-digital-green-certificates-gateway
+ datasource:
+ jndi-name: jdbc/dgc
+ jpa:
+ database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
+ hibernate:
+ ddl-auto: validate
+ liquibase:
+ enabled: true
+ change-log: classpath:db/changelog.xml
+ task:
+ scheduling:
+ pool:
+ size: 5
+management:
+ endpoints:
+ web:
+ exposure:
+ exclude: "*"
+ server:
+ port: -1
+dgc:
+ jrc:
+ url: https://covid-19-diagnostics.jrc.ec.europa.eu/devices/hsc-common-recognition-rat
+ proxy:
+ host: ${https.proxyHost:}
+ port: ${https.proxyPort:-1}
+ username: ${https.proxyUser:}
+ password: ${https.proxyPassword:}
+ validationRuleSchema: classpath:validation-rule.schema.json
+ trustAnchor:
+ keyStorePath: /ec/prod/app/san/dgc/dgc-ta.jks
+ keyStorePass: dgc-p4ssw0rd
+ certificateAlias: dgc_trust_anchor
+ cert-auth:
+ header-fields:
+ thumbprint: X-SSL-Client-SHA256
+ distinguished-name: X-SSL-Client-DN
+springdoc:
+ api-docs:
+ enabled: false
diff --git a/src/main/resources/db/changelog.xml b/src/main/resources/db/changelog.xml
new file mode 100644
index 00000000..25ee75b7
--- /dev/null
+++ b/src/main/resources/db/changelog.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/add-shedlock-table.xml b/src/main/resources/db/changelog/add-shedlock-table.xml
new file mode 100644
index 00000000..3f9095d6
--- /dev/null
+++ b/src/main/resources/db/changelog/add-shedlock-table.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/add-unique-constraints.xml b/src/main/resources/db/changelog/add-unique-constraints.xml
new file mode 100644
index 00000000..8d454a0f
--- /dev/null
+++ b/src/main/resources/db/changelog/add-unique-constraints.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/add-validation-rule-table.xml b/src/main/resources/db/changelog/add-validation-rule-table.xml
new file mode 100644
index 00000000..8ac6ff23
--- /dev/null
+++ b/src/main/resources/db/changelog/add-validation-rule-table.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/add-valueset-table.xml b/src/main/resources/db/changelog/add-valueset-table.xml
new file mode 100644
index 00000000..4568bfa1
--- /dev/null
+++ b/src/main/resources/db/changelog/add-valueset-table.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/create-audit-table.xml b/src/main/resources/db/changelog/create-audit-table.xml
new file mode 100644
index 00000000..630a6fc0
--- /dev/null
+++ b/src/main/resources/db/changelog/create-audit-table.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/fix-certificate-thumbprints.xml b/src/main/resources/db/changelog/fix-certificate-thumbprints.xml
new file mode 100644
index 00000000..ce51f139
--- /dev/null
+++ b/src/main/resources/db/changelog/fix-certificate-thumbprints.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ UPDATE trusted_party SET thumbprint = concat('00', thumbprint) WHERE length(thumbprint) = 62;
+
+
+
+ UPDATE signer_information SET thumbprint = concat('00', thumbprint) WHERE length(thumbprint) = 62;
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/increase-column-size-for-valueset.xml b/src/main/resources/db/changelog/increase-column-size-for-valueset.xml
new file mode 100644
index 00000000..605cf2c3
--- /dev/null
+++ b/src/main/resources/db/changelog/increase-column-size-for-valueset.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/init-tables.xml b/src/main/resources/db/changelog/init-tables.xml
new file mode 100644
index 00000000..0709924b
--- /dev/null
+++ b/src/main/resources/db/changelog/init-tables.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/snapshot.json b/src/main/resources/db/snapshot.json
new file mode 100644
index 00000000..03c99253
--- /dev/null
+++ b/src/main/resources/db/snapshot.json
@@ -0,0 +1,731 @@
+{
+ "snapshot": {
+ "created": "2021-06-28T15:18:17.131",
+ "database": {
+ "productVersion": "2021.1.2",
+ "shortName": "intellijPsiClass",
+ "majorVersion": "0",
+ "minorVersion": "0",
+ "user": "A34636994",
+ "productName": "JPA Buddy Intellij",
+ "url": "jpab?generationContext=60d05a16-aca2-4aeb-9823-8d67f6b942f1"
+ },
+ "metadata": {
+ "generationContext": {
+ "dbmsType": "mysql"
+ }
+ },
+ "objects": {
+ "liquibase.structure.core.Catalog": [
+ {
+ "catalog": {
+ "default": true,
+ "name": "JPA_BUDDY",
+ "snapshotId": "ff23152"
+ }
+ }
+ ],
+ "liquibase.structure.core.Column": [
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "authentication_sha256_fingerprint",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23159",
+ "type": {
+ "columnSize": "64!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "certificate_type",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23172",
+ "type": {
+ "columnSize": "255!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "certificate_type",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23200",
+ "type": {
+ "columnSize": "255!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "country",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23157",
+ "type": {
+ "columnSize": "2!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "country",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23169",
+ "type": {
+ "columnSize": "2!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "country",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23197",
+ "type": {
+ "columnSize": "2!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "country",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23183",
+ "type": {
+ "columnSize": "2!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "created_at",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23168",
+ "type": {
+ "typeName": "DATETIME"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "created_at",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23196",
+ "type": {
+ "typeName": "DATETIME"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "created_at",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23177",
+ "type": {
+ "typeName": "DATETIME"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "description",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23161",
+ "type": {
+ "columnSize": "64!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "event",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23160",
+ "type": {
+ "columnSize": "64!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "autoIncrementInformation": {
+ "incrementBy": "1!{java.math.BigInteger}",
+ "startWith": "1!{java.math.BigInteger}"
+ },
+ "certainDataType": false,
+ "name": "id",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23155",
+ "type": {
+ "typeName": "BIGINT"
+ }
+ }
+ },
+ {
+ "column": {
+ "autoIncrementInformation": {
+ "incrementBy": "1!{java.math.BigInteger}",
+ "startWith": "1!{java.math.BigInteger}"
+ },
+ "certainDataType": false,
+ "name": "id",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23167",
+ "type": {
+ "typeName": "BIGINT"
+ }
+ }
+ },
+ {
+ "column": {
+ "autoIncrementInformation": {
+ "incrementBy": "1!{java.math.BigInteger}",
+ "startWith": "1!{java.math.BigInteger}"
+ },
+ "certainDataType": false,
+ "name": "id",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23195",
+ "type": {
+ "typeName": "BIGINT"
+ }
+ }
+ },
+ {
+ "column": {
+ "autoIncrementInformation": {
+ "incrementBy": "1!{java.math.BigInteger}",
+ "startWith": "1!{java.math.BigInteger}"
+ },
+ "certainDataType": false,
+ "name": "id",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23176",
+ "type": {
+ "typeName": "BIGINT"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "id",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23186",
+ "snapshotId": "ff23188",
+ "type": {
+ "columnSize": "100!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "json",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23186",
+ "snapshotId": "ff23189",
+ "type": {
+ "columnSize": "1024000!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "raw_data",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23170",
+ "type": {
+ "columnSize": "4096!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "raw_data",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23198",
+ "type": {
+ "columnSize": "4096!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "rule_id",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23178",
+ "type": {
+ "columnSize": "100!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "signature",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23171",
+ "type": {
+ "columnSize": "6000!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "signature",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23199",
+ "type": {
+ "columnSize": "6000!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "signature",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23179",
+ "type": {
+ "columnSize": "10000!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "thumbprint",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23163",
+ "snapshotId": "ff23165",
+ "type": {
+ "columnSize": "64!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "thumbprint",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23191",
+ "snapshotId": "ff23193",
+ "type": {
+ "columnSize": "64!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "timestamp",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23156",
+ "type": {
+ "typeName": "DATETIME"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "type",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23184",
+ "type": {
+ "columnSize": "255!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "uploader_sha256_fingerprint",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23153",
+ "snapshotId": "ff23158",
+ "type": {
+ "columnSize": "64!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "valid_from",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23180",
+ "type": {
+ "typeName": "DATETIME"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "valid_to",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23181",
+ "type": {
+ "typeName": "DATETIME"
+ }
+ }
+ },
+ {
+ "column": {
+ "certainDataType": false,
+ "name": "version",
+ "nullable": false,
+ "relation": "liquibase.structure.core.Table#ff23174",
+ "snapshotId": "ff23182",
+ "type": {
+ "columnSize": "30!{java.lang.Integer}",
+ "typeName": "VARCHAR"
+ }
+ }
+ }
+ ],
+ "liquibase.structure.core.Index": [
+ {
+ "index": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23155"
+ ],
+ "name": "IX_PK_AUDIT_EVENT",
+ "snapshotId": "ff23154",
+ "table": "liquibase.structure.core.Table#ff23153",
+ "unique": true
+ }
+ },
+ {
+ "index": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23167"
+ ],
+ "name": "IX_PK_SIGNER_INFORMATION",
+ "snapshotId": "ff23166",
+ "table": "liquibase.structure.core.Table#ff23163",
+ "unique": true
+ }
+ },
+ {
+ "index": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23195"
+ ],
+ "name": "IX_PK_TRUSTED_PARTY",
+ "snapshotId": "ff23194",
+ "table": "liquibase.structure.core.Table#ff23191",
+ "unique": true
+ }
+ },
+ {
+ "index": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23176"
+ ],
+ "name": "IX_PK_VALIDATION_RULE",
+ "snapshotId": "ff23175",
+ "table": "liquibase.structure.core.Table#ff23174",
+ "unique": true
+ }
+ },
+ {
+ "index": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23188"
+ ],
+ "name": "IX_PK_VALUESET",
+ "snapshotId": "ff23187",
+ "table": "liquibase.structure.core.Table#ff23186",
+ "unique": true
+ }
+ }
+ ],
+ "liquibase.structure.core.PrimaryKey": [
+ {
+ "primaryKey": {
+ "backingIndex": "liquibase.structure.core.Index#ff23154",
+ "columns": [
+ "liquibase.structure.core.Column#ff23155"
+ ],
+ "name": "PK_AUDIT_EVENT",
+ "snapshotId": "ff23162",
+ "table": "liquibase.structure.core.Table#ff23153"
+ }
+ },
+ {
+ "primaryKey": {
+ "backingIndex": "liquibase.structure.core.Index#ff23166",
+ "columns": [
+ "liquibase.structure.core.Column#ff23167"
+ ],
+ "name": "PK_SIGNER_INFORMATION",
+ "snapshotId": "ff23173",
+ "table": "liquibase.structure.core.Table#ff23163"
+ }
+ },
+ {
+ "primaryKey": {
+ "backingIndex": "liquibase.structure.core.Index#ff23194",
+ "columns": [
+ "liquibase.structure.core.Column#ff23195"
+ ],
+ "name": "PK_TRUSTED_PARTY",
+ "snapshotId": "ff23201",
+ "table": "liquibase.structure.core.Table#ff23191"
+ }
+ },
+ {
+ "primaryKey": {
+ "backingIndex": "liquibase.structure.core.Index#ff23175",
+ "columns": [
+ "liquibase.structure.core.Column#ff23176"
+ ],
+ "name": "PK_VALIDATION_RULE",
+ "snapshotId": "ff23185",
+ "table": "liquibase.structure.core.Table#ff23174"
+ }
+ },
+ {
+ "primaryKey": {
+ "backingIndex": "liquibase.structure.core.Index#ff23187",
+ "columns": [
+ "liquibase.structure.core.Column#ff23188"
+ ],
+ "name": "PK_VALUESET",
+ "snapshotId": "ff23190",
+ "table": "liquibase.structure.core.Table#ff23186"
+ }
+ }
+ ],
+ "liquibase.structure.core.Schema": [
+ {
+ "schema": {
+ "catalog": "liquibase.structure.core.Catalog#ff23152",
+ "default": true,
+ "name": "JPA_BUDDY",
+ "snapshotId": "ff23151"
+ }
+ }
+ ],
+ "liquibase.structure.core.Table": [
+ {
+ "table": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23155",
+ "liquibase.structure.core.Column#ff23156",
+ "liquibase.structure.core.Column#ff23157",
+ "liquibase.structure.core.Column#ff23158",
+ "liquibase.structure.core.Column#ff23159",
+ "liquibase.structure.core.Column#ff23160",
+ "liquibase.structure.core.Column#ff23161"
+ ],
+ "indexes": [
+ "liquibase.structure.core.Index#ff23154"
+ ],
+ "name": "audit_event",
+ "primaryKey": "liquibase.structure.core.PrimaryKey#ff23162",
+ "schema": "liquibase.structure.core.Schema#ff23151",
+ "snapshotId": "ff23153"
+ }
+ },
+ {
+ "table": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23167",
+ "liquibase.structure.core.Column#ff23168",
+ "liquibase.structure.core.Column#ff23169",
+ "liquibase.structure.core.Column#ff23165",
+ "liquibase.structure.core.Column#ff23170",
+ "liquibase.structure.core.Column#ff23171",
+ "liquibase.structure.core.Column#ff23172"
+ ],
+ "indexes": [
+ "liquibase.structure.core.Index#ff23166"
+ ],
+ "name": "signer_information",
+ "primaryKey": "liquibase.structure.core.PrimaryKey#ff23173",
+ "schema": "liquibase.structure.core.Schema#ff23151",
+ "snapshotId": "ff23163",
+ "uniqueConstraints": [
+ "liquibase.structure.core.UniqueConstraint#ff23164"
+ ]
+ }
+ },
+ {
+ "table": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23195",
+ "liquibase.structure.core.Column#ff23196",
+ "liquibase.structure.core.Column#ff23197",
+ "liquibase.structure.core.Column#ff23193",
+ "liquibase.structure.core.Column#ff23198",
+ "liquibase.structure.core.Column#ff23199",
+ "liquibase.structure.core.Column#ff23200"
+ ],
+ "indexes": [
+ "liquibase.structure.core.Index#ff23194"
+ ],
+ "name": "trusted_party",
+ "primaryKey": "liquibase.structure.core.PrimaryKey#ff23201",
+ "schema": "liquibase.structure.core.Schema#ff23151",
+ "snapshotId": "ff23191",
+ "uniqueConstraints": [
+ "liquibase.structure.core.UniqueConstraint#ff23192"
+ ]
+ }
+ },
+ {
+ "table": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23176",
+ "liquibase.structure.core.Column#ff23177",
+ "liquibase.structure.core.Column#ff23178",
+ "liquibase.structure.core.Column#ff23179",
+ "liquibase.structure.core.Column#ff23180",
+ "liquibase.structure.core.Column#ff23181",
+ "liquibase.structure.core.Column#ff23182",
+ "liquibase.structure.core.Column#ff23183",
+ "liquibase.structure.core.Column#ff23184"
+ ],
+ "indexes": [
+ "liquibase.structure.core.Index#ff23175"
+ ],
+ "name": "validation_rule",
+ "primaryKey": "liquibase.structure.core.PrimaryKey#ff23185",
+ "schema": "liquibase.structure.core.Schema#ff23151",
+ "snapshotId": "ff23174"
+ }
+ },
+ {
+ "table": {
+ "columns": [
+ "liquibase.structure.core.Column#ff23188",
+ "liquibase.structure.core.Column#ff23189"
+ ],
+ "indexes": [
+ "liquibase.structure.core.Index#ff23187"
+ ],
+ "name": "valueset",
+ "primaryKey": "liquibase.structure.core.PrimaryKey#ff23190",
+ "schema": "liquibase.structure.core.Schema#ff23151",
+ "snapshotId": "ff23186"
+ }
+ }
+ ],
+ "liquibase.structure.core.UniqueConstraint": [
+ {
+ "uniqueConstraint": {
+ "clustered": false,
+ "columns": [
+ "liquibase.structure.core.Column#ff23165"
+ ],
+ "deferrable": false,
+ "disabled": false,
+ "initiallyDeferred": false,
+ "name": "UC_SIGNER_INFORMATION_THUMBPRINT",
+ "snapshotId": "ff23164",
+ "table": "liquibase.structure.core.Table#ff23163",
+ "validate": true
+ }
+ },
+ {
+ "uniqueConstraint": {
+ "clustered": false,
+ "columns": [
+ "liquibase.structure.core.Column#ff23193"
+ ],
+ "deferrable": false,
+ "disabled": false,
+ "initiallyDeferred": false,
+ "name": "UC_TRUSTED_PARTY_THUMBPRINT",
+ "snapshotId": "ff23192",
+ "table": "liquibase.structure.core.Table#ff23191",
+ "validate": true
+ }
+ }
+ ]
+ },
+ "snapshotControl": {
+ "snapshotControl": {
+ "includedType": [
+ "liquibase.structure.core.Catalog",
+ "liquibase.structure.core.Column",
+ "liquibase.structure.core.ForeignKey",
+ "liquibase.structure.core.Index",
+ "liquibase.structure.core.PrimaryKey",
+ "liquibase.structure.core.Schema",
+ "liquibase.structure.core.Sequence",
+ "liquibase.structure.core.Table",
+ "liquibase.structure.core.UniqueConstraint",
+ "liquibase.structure.core.View"
+ ]
+ }
+ }
+ }
+}
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
new file mode 100644
index 00000000..8a6a2211
--- /dev/null
+++ b/src/main/resources/logback-spring.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+ DEBUG
+
+
+
+
+ timestamp="%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", UTC}", level="%level", hostname="${HOSTNAME}", pid="${PID:-}", thread="%thread", class="%logger{40}", message="%replace(%replace(%m){'[\r\n]+', ', '}){'"', '\''}", trace="%X{traceId}", span="%X{spanId}", %X%n
+
+
+ utf8
+
+
+
+
+ ${catalina.base:-.}/logs/dgcg.log
+
+ ${catalina.base:-.}/logs/dgcg-%d{yyyy-MM-dd}.log
+ 90
+
+ true
+ true
+
+
+
+ timestamp="%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", UTC}", level="%level", hostname="${HOSTNAME}", pid="${PID:-}", thread="%thread", class="%logger{40}", message="%replace(%replace(%m){'[\r\n]+', ','}){'"', '\''}", exception="%replace(%ex){'[\r\n]+', ', '}", trace="%X{traceId}", span="%X{spanId}", %X%n%nopex
+
+
+ utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/validation-rule.schema.json b/src/main/resources/validation-rule.schema.json
new file mode 100644
index 00000000..454740ef
--- /dev/null
+++ b/src/main/resources/validation-rule.schema.json
@@ -0,0 +1,146 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://webgate.acceptance.ec.europa.eu/dgcg-json-api/validation-rule.schema.json",
+ "title": "EU DCC Validation Rule",
+ "description": "Rule to validate an issued EU Digital Covid Certificate.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "AffectedFields",
+ "Country",
+ "CertificateType",
+ "Description",
+ "Engine",
+ "EngineVersion",
+ "Identifier",
+ "Logic",
+ "SchemaVersion",
+ "Type",
+ "ValidFrom",
+ "ValidTo",
+ "Version"
+ ],
+ "properties": {
+ "Identifier": {
+ "type": "string",
+ "description": "The unique rule name",
+ "pattern": "^(GR|VR|TR|RR|IR)-[A-Z]{2}-\\d{4}$"
+ },
+ "Type": {
+ "type": "string",
+ "description": "Type of the rule",
+ "enum": [
+ "Acceptance",
+ "Invalidation"
+ ]
+ },
+ "Country": {
+ "type": "string",
+ "description": "ISO Country Code of rule owner",
+ "pattern": "^[A-Z]{2}$"
+ },
+ "Region": {
+ "type": "string",
+ "description": "Additional Region property to precise country property.",
+ "pattern": "^[A-Z0-9]{0,5}$"
+ },
+ "Version": {
+ "type": "string",
+ "description": "Version of the rule (Semver)",
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
+ },
+ "SchemaVersion": {
+ "type": "string",
+ "description": "Version of the used schema (Semver)",
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
+ },
+ "Engine": {
+ "type": "string",
+ "description": "Type of the RuleEngine"
+ },
+ "EngineVersion": {
+ "type": "string",
+ "description": "Version of the used engine (Semver)",
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
+ },
+ "CertificateType": {
+ "type": "string",
+ "description": "Type of the certificate",
+ "enum": [
+ "General",
+ "Test",
+ "Vaccination",
+ "Recovery"
+ ]
+ },
+ "Description": {
+ "type": "array",
+ "description": "Array of human readable description of the rule",
+ "items": {
+ "type": "object",
+ "required": [
+ "lang",
+ "desc"
+ ],
+ "properties": {
+ "lang": {
+ "type": "string",
+ "description": "Language of the description",
+ "pattern": "^([a-z]{2}|[a-z]{2}-[a-z]{2})$"
+ },
+ "desc": {
+ "type": "string",
+ "description": "Description of this rule in specified language",
+ "minLength": 20
+ }
+ }
+ },
+ "contains": {
+ "type": "object",
+ "required": [
+ "lang",
+ "desc"
+ ],
+ "properties": {
+ "lang": {
+ "type": "string",
+ "description": "Language of the description",
+ "pattern": "^en$"
+ },
+ "desc": {
+ "type": "string",
+ "description": "Human readable description of this rule in English language",
+ "minLength": 20
+ }
+ }
+ },
+ "minItems": 1
+ },
+ "ValidFrom": {
+ "type": "string",
+ "description": "Start validity of the rule as ISO 8601 Timestamp (without ms, with timezone)",
+ "format": "date-time",
+ "pattern": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([-+][0-2]\\d:[0-5]\\d|Z)$"
+ },
+ "ValidTo": {
+ "type": "string",
+ "description": "End validity of the rule as ISO 8601 Timestamp (without ms, with timezone)",
+ "format": "date-time",
+ "pattern": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([-+][0-2]\\d:[0-5]\\d|Z)$"
+ },
+ "AffectedFields": {
+ "type": "array",
+ "description": "Fields of the payload which are used by the rule.",
+ "items": {
+ "type": "string",
+ "description": "Affected field of payload"
+ },
+ "minItems": 1
+ },
+ "Logic": {
+ "type": "object",
+ "description": "The logic payload in JSON",
+ "minProperties": 1
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CountryListIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CountryListIntegrationTest.java
new file mode 100644
index 00000000..3b6ac86c
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CountryListIntegrationTest.java
@@ -0,0 +1,88 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.restapi.controller;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class CountryListIntegrationTest {
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ TrustedPartyRepository trustedPartyRepository;
+
+ @Autowired
+ DgcConfigProperties dgcConfigProperties;
+
+ private static final String countryCode = "EU";
+ private static final String authCertSubject = "C=EU";
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @BeforeEach
+ void testData() {
+ trustedPartyRepository.deleteAll();
+ }
+
+ @Test
+ void testGetTrustedParties() throws Exception {
+ // Insert some Certificates for random Countries
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, "AA");
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, "AB");
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, "AC");
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, "AD");
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/countrylist")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.length()").value(5))
+ .andExpect(jsonPath("$[0]").value("AA"))
+ .andExpect(jsonPath("$[1]").value("AB"))
+ .andExpect(jsonPath("$[2]").value("AC"))
+ .andExpect(jsonPath("$[3]").value("AD"))
+ .andExpect(jsonPath("$[4]").value(countryCode));
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/SignerCertificateIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/SignerCertificateIntegrationTest.java
new file mode 100644
index 00000000..c49bf4db
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/SignerCertificateIntegrationTest.java
@@ -0,0 +1,326 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.restapi.controller;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.AuditEventRepository;
+import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository;
+import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils;
+import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder;
+import eu.europa.ec.dgc.signing.SignedCertificateMessageParser;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Optional;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class SignerCertificateIntegrationTest {
+
+ @Autowired
+ DgcConfigProperties dgcConfigProperties;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ @Autowired
+ DgcTestKeyStore dgcTestKeyStore;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ SignerInformationRepository signerInformationRepository;
+
+ @Autowired
+ AuditEventRepository auditEventRepository;
+ @Autowired
+ private MockMvc mockMvc;
+
+ private static final String countryCode = "EU";
+ private static final String authCertSubject = "C=" + countryCode;
+
+ @Test
+ void testSuccessfulUpload() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ // immediately parse the message to get the signature from the signed message
+ String signature = new SignedCertificateMessageParser(payload).getSignature();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ Assertions.assertEquals(signerInformationEntitiesInDb + 1, signerInformationRepository.count());
+ Optional createdSignerInformationEntity =
+ signerInformationRepository.getFirstByThumbprint(certificateUtils.getCertThumbprint(payloadCertificate));
+
+ Assertions.assertTrue(createdSignerInformationEntity.isPresent());
+
+ Assertions.assertEquals(auditEventEntitiesInDb+1, auditEventRepository.count());
+ Assertions.assertEquals(SignerInformationEntity.CertificateType.DSC, createdSignerInformationEntity.get().getCertificateType());
+ Assertions.assertEquals(countryCode, createdSignerInformationEntity.get().getCountry());
+ Assertions.assertEquals(signature, createdSignerInformationEntity.get().getSignature());
+ Assertions.assertEquals(Base64.getEncoder().encodeToString(payloadCertificate.getEncoded()), createdSignerInformationEntity.get().getRawData());
+ }
+
+ @Test
+ void testUploadFailedConflict() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isConflict());
+ Assertions.assertEquals(auditEventEntitiesInDb+1, auditEventRepository.count());
+ Assertions.assertEquals(signerInformationEntitiesInDb + 1, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedInvalidCSCA() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ // sign with TrustAnchor
+ X509Certificate cscaCertificate = dgcTestKeyStore.getTrustAnchor();
+ PrivateKey cscaPrivateKey = dgcTestKeyStore.getTrustAnchorPrivateKey();
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ Assertions.assertEquals(auditEventEntitiesInDb+1, auditEventRepository.count());
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedInvalidCSCAWrongCountryCode() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ // sign with CSCA from another country
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "XX");
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, "XX");
+
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ Assertions.assertEquals(auditEventEntitiesInDb+1, auditEventRepository.count());
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedPayloadCertCountryWrong() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, "XX", "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ Assertions.assertEquals(auditEventEntitiesInDb+1, auditEventRepository.count());
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedWrongSignerCertificate() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "XX");
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, "XX");
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ Assertions.assertEquals(auditEventEntitiesInDb+1, auditEventRepository.count());
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedInvalidCmsMessage() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ String payload = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(new X509CertificateHolder(payloadCertificate.getEncoded()))
+ .buildAsString();
+
+ // randomly play a little bit inside the base64 string
+ payload = payload.replace(payload.substring(10, 50), payload.substring(80, 120));
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java
new file mode 100644
index 00000000..40affd9a
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java
@@ -0,0 +1,490 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.restapi.controller;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository;
+import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository;
+import eu.europa.ec.dgc.gateway.restapi.dto.CertificateTypeDto;
+import eu.europa.ec.dgc.gateway.restapi.dto.TrustListDto;
+import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils;
+import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyPairGenerator;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.time.ZonedDateTime;
+import java.util.Base64;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class TrustListIntegrationTest {
+
+ @Autowired
+ SignerInformationRepository signerInformationRepository;
+
+ @Autowired
+ TrustedPartyRepository trustedPartyRepository;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ DgcConfigProperties dgcConfigProperties;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ @Autowired
+ DgcTestKeyStore dgcTestKeyStore;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private static final String countryCode = "EU";
+ private static final String authCertSubject = "C=" + countryCode;
+
+ X509Certificate certUploadDe, certUploadEu, certCscaDe, certCscaEu, certAuthDe, certAuthEu, certDscDe, certDscEu;
+
+ @BeforeEach
+ void testData() throws Exception {
+ trustedPartyRepository.deleteAll();
+ signerInformationRepository.deleteAll();
+
+ certUploadDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "DE");
+ certUploadEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "EU");
+ certCscaDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "DE");
+ certCscaEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "EU");
+ certAuthDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE");
+ certAuthEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU");
+
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec");
+ certDscDe = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "Test");
+ certDscEu = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EU", "Test");
+
+ signerInformationRepository.save(new SignerInformationEntity(
+ null,
+ ZonedDateTime.now(),
+ "DE",
+ certificateUtils.getCertThumbprint(certDscDe),
+ Base64.getEncoder().encodeToString(certDscDe.getEncoded()),
+ "sig1",
+ SignerInformationEntity.CertificateType.DSC
+ ));
+
+ signerInformationRepository.save(new SignerInformationEntity(
+ null,
+ ZonedDateTime.now(),
+ "EU",
+ certificateUtils.getCertThumbprint(certDscEu),
+ Base64.getEncoder().encodeToString(certDscEu.getEncoded()),
+ "sig2",
+ SignerInformationEntity.CertificateType.DSC
+ ));
+ }
+
+ @Test
+ void testTrustListDownloadNoFilter() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1"))
+ .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2"))
+ .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 8));
+ }
+
+ @Test
+ void testTrustListDownloadFilterByType() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList/AUTHENTICATION")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 2));
+
+ mockMvc.perform(get("/trustList/UPLOAD")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListLength(c, 2));
+
+ mockMvc.perform(get("/trustList/CSCA")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListLength(c, 2));
+
+ mockMvc.perform(get("/trustList/DSC")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1"))
+ .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2"))
+ .andExpect(c -> assertTrustListLength(c, 2));
+ }
+
+ @Test
+ void testTrustListDownloadFilterByTypeCaseInsensitive() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList/aUtHeNtiCaTiOn")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 2));
+
+ mockMvc.perform(get("/trustList/uploAd")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListLength(c, 2));
+
+ mockMvc.perform(get("/trustList/csCA")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListLength(c, 2));
+
+ mockMvc.perform(get("/trustList/dsc")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1"))
+ .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2"))
+ .andExpect(c -> assertTrustListLength(c, 2));
+ }
+
+ @Test
+ void testTrustListDownloadFilterByTypeAndCountry() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList/AUTHENTICATION/DE")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/AUTHENTICATION/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/UPLOAD/DE")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/UPLOAD/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/CSCA/DE")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/CSCA/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/DSC/DE")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1"))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/DSC/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2"))
+ .andExpect(c -> assertTrustListLength(c, 1));
+ }
+
+ @Test
+ void testTrustListDownloadFilterByTypeAndCountryLowercase() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList/AUTHENTICATION/de")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/AUTHENTICATION/eu")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/UPLOAD/de")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/UPLOAD/eu")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/CSCA/de")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/CSCA/eu")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/DSC/de")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1"))
+ .andExpect(c -> assertTrustListLength(c, 1));
+
+ mockMvc.perform(get("/trustList/DSC/eu")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2"))
+ .andExpect(c -> assertTrustListLength(c, 1));
+ }
+
+ @Test
+ void testTrustListDownloadEmptyList() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ signerInformationRepository.deleteAll();
+
+ mockMvc.perform(get("/trustList/DSC")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().json("[]"));
+ }
+
+ @Test
+ void testTrustListWrongType() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList/XXX")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ void testTrustListWrongCountryCode() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/trustList/DSC/XXX")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+ }
+
+ private void assertTrustListItem(MvcResult result, X509Certificate certificate, String country, CertificateTypeDto certificateTypeDto, String signature) throws CertificateEncodingException, UnsupportedEncodingException, JsonProcessingException {
+ ObjectMapper objectMapper = new ObjectMapper()
+ .registerModule(new JavaTimeModule());
+ List trustList = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {
+ });
+
+ Optional trustListOptional = trustList
+ .stream()
+ .filter(tl -> tl.getKid().equals(certificateUtils.getCertKid(certificate)))
+ .findFirst();
+
+ Assertions.assertTrue(trustListOptional.isPresent());
+
+ TrustListDto trustListItem = trustListOptional.get();
+
+ Assertions.assertEquals(certificateUtils.getCertKid(certificate), trustListItem.getKid());
+ Assertions.assertEquals(country, trustListItem.getCountry());
+ Assertions.assertEquals(certificateTypeDto, trustListItem.getCertificateType());
+ Assertions.assertEquals(certificateUtils.getCertThumbprint(certificate), trustListItem.getThumbprint());
+ Assertions.assertEquals(Base64.getEncoder().encodeToString(certificate.getEncoded()), trustListItem.getRawData());
+
+ if (signature != null) {
+ Assertions.assertEquals(signature, trustListItem.getSignature());
+ }
+ }
+
+ private void assertTrustListLength(MvcResult result, int expectedLength) throws UnsupportedEncodingException, JsonProcessingException {
+ ObjectMapper objectMapper = new ObjectMapper()
+ .registerModule(new JavaTimeModule());
+ List trustList = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {
+ });
+ Assertions.assertEquals(expectedLength, trustList.size());
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/ValidationRuleIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/ValidationRuleIntegrationTest.java
new file mode 100644
index 00000000..ea759c3c
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/ValidationRuleIntegrationTest.java
@@ -0,0 +1,1215 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.restapi.controller;
+
+import static eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils.assertEquals;
+import static eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils.getDummyValidationRule;
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer;
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.connector.model.ValidationRule;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.entity.ValidationRuleEntity;
+import eu.europa.ec.dgc.gateway.repository.AuditEventRepository;
+import eu.europa.ec.dgc.gateway.repository.ValidationRuleRepository;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.signing.SignedStringMessageBuilder;
+import eu.europa.ec.dgc.signing.SignedStringMessageParser;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@Slf4j
+class ValidationRuleIntegrationTest {
+
+ @Autowired
+ DgcConfigProperties dgcConfigProperties;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ AuditEventRepository auditEventRepository;
+
+ @Autowired
+ ValidationRuleRepository validationRuleRepository;
+
+ ObjectMapper objectMapper;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
+ .parseCaseInsensitive()
+ .append(ISO_LOCAL_DATE)
+ .appendLiteral('T')
+ .appendValue(HOUR_OF_DAY, 2)
+ .appendLiteral(':')
+ .appendValue(MINUTE_OF_HOUR, 2)
+ .optionalStart()
+ .appendLiteral(':')
+ .appendValue(SECOND_OF_MINUTE, 2)
+ .appendOffsetId()
+ .toFormatter();
+
+ private static final String countryCode = "EU";
+ private static final String authCertSubject = "C=" + countryCode;
+
+ @BeforeEach
+ public void setup() {
+ validationRuleRepository.deleteAll();
+ auditEventRepository.deleteAll();
+
+ objectMapper = new ObjectMapper();
+
+ JavaTimeModule javaTimeModule = new JavaTimeModule();
+ javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(
+ new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd'T'HH:mm:ssXXX").toFormatter()
+ ));
+
+ objectMapper.registerModule(javaTimeModule);
+ objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ }
+
+ @Test
+ void testSuccessfulUpload() throws Exception {
+ long validationRulesInDb = validationRuleRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ Assertions.assertEquals(validationRulesInDb + 1, validationRuleRepository.count());
+ Optional createdValidationRule =
+ validationRuleRepository.getByRuleIdAndVersion(validationRule.getIdentifier(), validationRule.getVersion());
+
+ Assertions.assertTrue(createdValidationRule.isPresent());
+
+ Assertions.assertEquals(auditEventEntitiesInDb + 1, auditEventRepository.count());
+ Assertions.assertEquals(validationRule.getValidFrom().toEpochSecond(), createdValidationRule.get().getValidFrom().toEpochSecond());
+ Assertions.assertEquals(validationRule.getValidTo().toEpochSecond(), createdValidationRule.get().getValidTo().toEpochSecond());
+ Assertions.assertEquals(validationRule.getCountry(), createdValidationRule.get().getCountry());
+ Assertions.assertEquals(validationRule.getType().toUpperCase(Locale.ROOT), createdValidationRule.get().getValidationRuleType().toString());
+
+ SignedStringMessageParser parser = new SignedStringMessageParser(createdValidationRule.get().getCms());
+ ValidationRule parsedValidationRule = objectMapper.readValue(parser.getPayload(), ValidationRule.class);
+
+ assertEquals(validationRule, parsedValidationRule);
+ }
+
+ @Test
+ void testSuccessfulUploadWithoutRegionProperty() throws Exception {
+ long validationRulesInDb = validationRuleRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setRegion(null);
+ String json = objectMapper.writeValueAsString(validationRule);
+ json = json.replace("\"Region\":null,", "");
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(json)
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ Assertions.assertEquals(validationRulesInDb + 1, validationRuleRepository.count());
+ Optional createdValidationRule =
+ validationRuleRepository.getByRuleIdAndVersion(validationRule.getIdentifier(), validationRule.getVersion());
+
+ Assertions.assertTrue(createdValidationRule.isPresent());
+
+ Assertions.assertEquals(auditEventEntitiesInDb + 1, auditEventRepository.count());
+ Assertions.assertEquals(validationRule.getValidFrom().toEpochSecond(), createdValidationRule.get().getValidFrom().toEpochSecond());
+ Assertions.assertEquals(validationRule.getValidTo().toEpochSecond(), createdValidationRule.get().getValidTo().toEpochSecond());
+ Assertions.assertEquals(validationRule.getCountry(), createdValidationRule.get().getCountry());
+ Assertions.assertEquals(validationRule.getType().toUpperCase(Locale.ROOT), createdValidationRule.get().getValidationRuleType().toString());
+
+ SignedStringMessageParser parser = new SignedStringMessageParser(createdValidationRule.get().getCms());
+ ValidationRule parsedValidationRule = objectMapper.readValue(parser.getPayload(), ValidationRule.class);
+
+ assertEquals(validationRule, parsedValidationRule);
+ }
+
+ @Test
+ void testInputOnlyContainsJson() throws Exception {
+ long validationRulesInDb = validationRuleRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule) + "\n" + objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule) + "x")
+ .buildAsString();
+
+ authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest());
+
+ Assertions.assertEquals(validationRulesInDb, validationRuleRepository.count());
+ }
+
+ @Test
+ void testJsonSchemaValidation() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ Map invalidValidationRules = new HashMap<>();
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setIdentifier("XXXXXXXX");
+ invalidValidationRules.put("Invalid ID Pattern", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setType("XXXXX");
+ invalidValidationRules.put("Invalid Type", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setCountry("EUX");
+ invalidValidationRules.put("Invalid Country Pattern", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setRegion("XXXXXX");
+ invalidValidationRules.put("Invalid Region Pattern", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setVersion("1.0.0.0");
+ invalidValidationRules.put("Invalid Version Pattern", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setSchemaVersion("1.0.0.0");
+ invalidValidationRules.put("Invalid Schema Version Pattern", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setEngineVersion("1.2.3.aaaaa");
+ invalidValidationRules.put("Invalid EngineVersion Pattern", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setDescription(List.of(new ValidationRule.DescriptionItem("xx", "1".repeat(20))));
+ invalidValidationRules.put("Missing Description EN", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.getDescription().get(0).setDescription("shorttext");
+ invalidValidationRules.put("Description to short", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setAffectedFields(Collections.emptyList());
+ invalidValidationRules.put("AffectedFields No Values", validationRule);
+
+ validationRule = getDummyValidationRule();
+ validationRule.setLogic(JsonNodeFactory.instance.objectNode());
+ invalidValidationRules.put("Logic Empty", validationRule);
+
+ for (String ruleKey : invalidValidationRules.keySet()) {
+ log.info("JSON Schema Check: {}", ruleKey);
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(invalidValidationRules.get(ruleKey)))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x200"));
+ }
+ }
+
+ @Test
+ void testValidationCountry() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setIdentifier("GR-DE-0001");
+ validationRule.setCountry("DE");
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isForbidden())
+ .andExpect(jsonPath("$.code").value("0x210"));
+
+ validationRule.setCountry("EU");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isForbidden())
+ .andExpect(jsonPath("$.code").value("0x210"));
+ }
+
+ @Test
+ void testValidationVersion() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x220"));
+
+ validationRule.setVersion("0.9.0");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x220"));
+ }
+
+ @Test
+ void testValidationUploadCert() throws Exception {
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(
+ certificateUtils.convertCertificate(trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "EU")),
+ trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, "EU"))
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x230"));
+ }
+
+ @Test
+ void testValidationTimestamps() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setValidFrom(ZonedDateTime.now().plus(1, ChronoUnit.DAYS));
+ validationRule.setValidTo(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x240"));
+
+ validationRule = getDummyValidationRule();
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ validationRule.setVersion("1.0.1");
+ validationRule.setValidFrom(validationRule.getValidFrom().minus(1, ChronoUnit.SECONDS));
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x240"));
+
+ validationRule.setValidFrom(ZonedDateTime.now().plus(4, ChronoUnit.WEEKS));
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x240"));
+ }
+
+ @Test
+ void testValidationTimestamps2() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setIdentifier("IR-EU-0001");
+ validationRule.setType("Invalidation");
+ validationRule.setValidFrom(ZonedDateTime.now().plus(1, ChronoUnit.SECONDS));
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ validationRule = getDummyValidationRule();
+ validationRule.setValidFrom(ZonedDateTime.now());
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x240"));
+ }
+
+ @Test
+ void testValidationTimestamps3() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setValidFrom(ZonedDateTime.now().plus(3, ChronoUnit.DAYS));
+ validationRule.setValidTo(ZonedDateTime.now()
+ .plus(6, ChronoUnit.DAYS)
+ .minus(1, ChronoUnit.SECONDS));
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x240"));
+ }
+
+ @Test
+ void testValidationRuleId() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setIdentifier("GR-EU-0001");
+ validationRule.setType("Invalidation");
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+
+ validationRule.setIdentifier("IR-EU-0001");
+ validationRule.setType("Acceptance");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+
+ validationRule.setIdentifier("IR-EU-0001");
+ validationRule.setType("Invalidation");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ validationRule.setIdentifier("GR-EU-0001");
+ validationRule.setType("Acceptance");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+ }
+
+ @Test
+ void testValidationRuleInvalidIdPrefix() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+ validationRule.setIdentifier("TR-EU-0001");
+ validationRule.setCertificateType("Vaccination");
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+
+ validationRule.setIdentifier("VR-EU-0001");
+ validationRule.setCertificateType("Test");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+
+ validationRule.setIdentifier("RR-EU-0001");
+ validationRule.setCertificateType("Vaccination");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+
+ validationRule.setIdentifier("GR-EU-0001");
+ validationRule.setCertificateType("Vaccination");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+ }
+
+ @Test
+ void testDelete() throws Exception {
+ long validationRulesInDb = validationRuleRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ validationRule.setVersion("1.0.1");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ Assertions.assertEquals(validationRulesInDb + 2, validationRuleRepository.count());
+
+ String deletePayload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(validationRule.getIdentifier())
+ .buildAsString();
+
+ mockMvc.perform(delete("/rules")
+ .content(deletePayload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isNoContent());
+
+ Assertions.assertEquals(validationRulesInDb, validationRuleRepository.count());
+ }
+
+ @Test
+ void testDeleteFailNotFound() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString("IR-EU-0001"))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(delete("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ void testDeleteFailInvalidUploadCertificate() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString("IR-EU-0001"))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(delete("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x230"));
+ }
+
+ @Test
+ void testDeleteFailInvalidIdString() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString("XXXX-TESST-!!!!!"))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(delete("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code").value("0x250"));
+ }
+
+ @Test
+ void testDeleteFailInvalidCountryCode() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString("IR-DE-0001"))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(delete("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isForbidden())
+ .andExpect(jsonPath("$.code").value("0x210"));
+ }
+
+ @Test
+ void testDownloadReturnAll() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule1 = getDummyValidationRule();
+ validationRule1.setValidFrom(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ String payload1 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule1))
+ .buildAsString();
+
+ ValidationRuleEntity vr1 = new ValidationRuleEntity();
+ vr1.setRuleId(validationRule1.getIdentifier());
+ vr1.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule1.getType().toUpperCase(Locale.ROOT)));
+ vr1.setValidTo(validationRule1.getValidTo());
+ vr1.setValidFrom(validationRule1.getValidFrom());
+ vr1.setCountry(validationRule1.getCountry());
+ vr1.setCms(payload1);
+ vr1.setVersion(validationRule1.getVersion());
+
+ validationRuleRepository.save(vr1);
+
+ ValidationRule validationRule2 = getDummyValidationRule();
+ validationRule2.setValidFrom(ZonedDateTime.now().plus(2, ChronoUnit.DAYS));
+ validationRule2.setVersion("1.0.1");
+
+ String payload2 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule2))
+ .buildAsString();
+
+ ValidationRuleEntity vr2 = new ValidationRuleEntity();
+ vr2.setRuleId(validationRule2.getIdentifier());
+ vr2.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule2.getType().toUpperCase(Locale.ROOT)));
+ vr2.setValidTo(validationRule2.getValidTo());
+ vr2.setValidFrom(validationRule2.getValidFrom());
+ vr2.setCountry(validationRule2.getCountry());
+ vr2.setCms(payload2);
+ vr2.setVersion(validationRule2.getVersion());
+
+ validationRuleRepository.save(vr2);
+
+ ValidationRule validationRule3 = getDummyValidationRule();
+ validationRule3.setIdentifier("GR-EU-0002");
+
+ String payload3 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule3))
+ .buildAsString();
+
+ ValidationRuleEntity vr3 = new ValidationRuleEntity();
+ vr3.setRuleId(validationRule3.getIdentifier());
+ vr3.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule3.getType().toUpperCase(Locale.ROOT)));
+ vr3.setValidTo(validationRule3.getValidTo());
+ vr3.setValidFrom(validationRule3.getValidFrom());
+ vr3.setCountry(validationRule3.getCountry());
+ vr3.setCms(payload3);
+ vr3.setVersion(validationRule3.getVersion());
+
+ validationRuleRepository.save(vr3);
+
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/rules/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.['GR-EU-0001'].length()").value(2))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].version").value(vr2.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].cms").value(vr2.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].validTo").value(vr2.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].validFrom").value(vr2.getValidFrom().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].version").value(vr1.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].cms").value(vr1.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].validTo").value(vr1.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].validFrom").value(vr1.getValidFrom().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0002'].length()").value(1))
+ .andExpect(jsonPath("$.['GR-EU-0002'][0].version").value(vr3.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0002'][0].cms").value(vr3.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0002'][0].validTo").value(vr3.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0002'][0].validFrom").value(vr3.getValidFrom().format(formatter)));
+ }
+
+ @Test
+ void testDownloadReturnOnlyValid() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule1 = getDummyValidationRule();
+ validationRule1.setValidFrom(ZonedDateTime.now().minus(4, ChronoUnit.DAYS));
+
+ String payload1 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule1))
+ .buildAsString();
+
+ ValidationRuleEntity vr1 = new ValidationRuleEntity();
+ vr1.setRuleId(validationRule1.getIdentifier());
+ vr1.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule1.getType().toUpperCase(Locale.ROOT)));
+ vr1.setValidTo(validationRule1.getValidTo());
+ vr1.setValidFrom(validationRule1.getValidFrom());
+ vr1.setCountry(validationRule1.getCountry());
+ vr1.setCms(payload1);
+ vr1.setVersion(validationRule1.getVersion());
+
+ validationRuleRepository.save(vr1);
+
+ ValidationRule validationRule2 = getDummyValidationRule();
+ validationRule2.setValidFrom(ZonedDateTime.now().minus(2, ChronoUnit.DAYS));
+ validationRule2.setVersion("1.0.1");
+
+ String payload2 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule2))
+ .buildAsString();
+
+ ValidationRuleEntity vr2 = new ValidationRuleEntity();
+ vr2.setRuleId(validationRule2.getIdentifier());
+ vr2.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule2.getType().toUpperCase(Locale.ROOT)));
+ vr2.setValidTo(validationRule2.getValidTo());
+ vr2.setValidFrom(validationRule2.getValidFrom());
+ vr2.setCountry(validationRule2.getCountry());
+ vr2.setCms(payload2);
+ vr2.setVersion(validationRule2.getVersion());
+
+ validationRuleRepository.save(vr2);
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/rules/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.['GR-EU-0001'].length()").value(1))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].version").value(vr2.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].cms").value(vr2.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].validTo").value(vr2.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].validFrom").value(vr2.getValidFrom().format(formatter)));
+ }
+
+ @Test
+ void testDownloadDbContainsOnlyRulesValidInFutureShouldReturnAll() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule1 = getDummyValidationRule();
+ validationRule1.setValidFrom(ZonedDateTime.now().plus(1, ChronoUnit.DAYS));
+
+ String payload1 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule1))
+ .buildAsString();
+
+ ValidationRuleEntity vr1 = new ValidationRuleEntity();
+ vr1.setRuleId(validationRule1.getIdentifier());
+ vr1.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule1.getType().toUpperCase(Locale.ROOT)));
+ vr1.setValidTo(validationRule1.getValidTo());
+ vr1.setValidFrom(validationRule1.getValidFrom());
+ vr1.setCountry(validationRule1.getCountry());
+ vr1.setCms(payload1);
+ vr1.setVersion(validationRule1.getVersion());
+
+ validationRuleRepository.save(vr1);
+
+ ValidationRule validationRule2 = getDummyValidationRule();
+ validationRule2.setValidFrom(ZonedDateTime.now().plus(2, ChronoUnit.DAYS));
+ validationRule2.setVersion("1.0.1");
+
+ String payload2 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule2))
+ .buildAsString();
+
+ ValidationRuleEntity vr2 = new ValidationRuleEntity();
+ vr2.setRuleId(validationRule2.getIdentifier());
+ vr2.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule2.getType().toUpperCase(Locale.ROOT)));
+ vr2.setValidTo(validationRule2.getValidTo());
+ vr2.setValidFrom(validationRule2.getValidFrom());
+ vr2.setCountry(validationRule2.getCountry());
+ vr2.setCms(payload2);
+ vr2.setVersion(validationRule2.getVersion());
+
+ validationRuleRepository.save(vr2);
+
+ ValidationRule validationRule3 = getDummyValidationRule();
+ validationRule3.setValidFrom(ZonedDateTime.now().plus(3, ChronoUnit.DAYS));
+ validationRule3.setVersion("1.1.0");
+
+ String payload3 = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule3))
+ .buildAsString();
+
+ ValidationRuleEntity vr3 = new ValidationRuleEntity();
+ vr3.setRuleId(validationRule3.getIdentifier());
+ vr3.setValidationRuleType(ValidationRuleEntity.ValidationRuleType.valueOf(validationRule3.getType().toUpperCase(Locale.ROOT)));
+ vr3.setValidTo(validationRule3.getValidTo());
+ vr3.setValidFrom(validationRule3.getValidFrom());
+ vr3.setCountry(validationRule3.getCountry());
+ vr3.setCms(payload3);
+ vr3.setVersion(validationRule3.getVersion());
+
+ validationRuleRepository.save(vr3);
+
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/rules/EU")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.['GR-EU-0001'].length()").value(3))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].version").value(vr3.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].cms").value(vr3.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].validTo").value(vr3.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][0].validFrom").value(vr3.getValidFrom().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].version").value(vr2.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].cms").value(vr2.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].validTo").value(vr2.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][1].validFrom").value(vr2.getValidFrom().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][2].version").value(vr1.getVersion()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][2].cms").value(vr1.getCms()))
+ .andExpect(jsonPath("$.['GR-EU-0001'][2].validTo").value(vr1.getValidTo().format(formatter)))
+ .andExpect(jsonPath("$.['GR-EU-0001'][2].validFrom").value(vr1.getValidFrom().format(formatter)));
+
+ }
+
+ @Test
+ void testDeleteAliasEndpoint() throws Exception {
+ long validationRulesInDb = validationRuleRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ validationRule.setVersion("1.0.1");
+
+ payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ Assertions.assertEquals(validationRulesInDb + 2, validationRuleRepository.count());
+
+ String deletePayload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(validationRule.getIdentifier())
+ .buildAsString();
+
+ mockMvc.perform(post("/rules/delete")
+ .content(deletePayload)
+ .contentType("application/cms")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isNoContent());
+
+ Assertions.assertEquals(validationRulesInDb, validationRuleRepository.count());
+ }
+
+ @Test
+ void testSuccessfulUploadWithOldContentType() throws Exception {
+ long validationRulesInDb = validationRuleRepository.count();
+ long auditEventEntitiesInDb = auditEventRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ PrivateKey signerPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ ValidationRule validationRule = getDummyValidationRule();
+
+ String payload = new SignedStringMessageBuilder()
+ .withSigningCertificate(certificateUtils.convertCertificate(signerCertificate), signerPrivateKey)
+ .withPayload(objectMapper.writeValueAsString(validationRule))
+ .buildAsString();
+
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/rules")
+ .content(payload)
+ .contentType("application/cms-text")
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isCreated());
+
+ Assertions.assertEquals(validationRulesInDb + 1, validationRuleRepository.count());
+ Optional createdValidationRule =
+ validationRuleRepository.getByRuleIdAndVersion(validationRule.getIdentifier(), validationRule.getVersion());
+
+ Assertions.assertTrue(createdValidationRule.isPresent());
+
+ Assertions.assertEquals(auditEventEntitiesInDb + 1, auditEventRepository.count());
+ Assertions.assertEquals(validationRule.getValidFrom().toEpochSecond(), createdValidationRule.get().getValidFrom().toEpochSecond());
+ Assertions.assertEquals(validationRule.getValidTo().toEpochSecond(), createdValidationRule.get().getValidTo().toEpochSecond());
+ Assertions.assertEquals(validationRule.getCountry(), createdValidationRule.get().getCountry());
+ Assertions.assertEquals(validationRule.getType().toUpperCase(Locale.ROOT), createdValidationRule.get().getValidationRuleType().toString());
+
+ SignedStringMessageParser parser = new SignedStringMessageParser(createdValidationRule.get().getCms());
+ ValidationRule parsedValidationRule = objectMapper.readValue(parser.getPayload(), ValidationRule.class);
+
+ assertEquals(validationRule, parsedValidationRule);
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/ValuesetIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/ValuesetIntegrationTest.java
new file mode 100644
index 00000000..9d5e40c9
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/ValuesetIntegrationTest.java
@@ -0,0 +1,137 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.restapi.controller;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.entity.ValuesetEntity;
+import eu.europa.ec.dgc.gateway.repository.ValuesetRepository;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class ValuesetIntegrationTest {
+
+ @Autowired
+ ValuesetRepository valuesetRepository;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ DgcConfigProperties dgcConfigProperties;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private static final String countryCode = "EU";
+ private static final String authCertSubject = "C=" + countryCode;
+
+ private static final ValuesetEntity valuesetEntity1 =
+ new ValuesetEntity("vs-dummy-1", "{ \"key1\": \"content1\" }");
+ private static final ValuesetEntity valuesetEntity2 =
+ new ValuesetEntity("vs-dummy-2", "{ \"key2\": \"content2\" }");
+ private static final ValuesetEntity valuesetEntity3 =
+ new ValuesetEntity("vs-dummy-3", "{ \"key3\": \"content3\" }");
+
+ @BeforeEach
+ void testData() {
+ valuesetRepository.deleteAll();
+
+ valuesetRepository.save(valuesetEntity1);
+ valuesetRepository.save(valuesetEntity2);
+ valuesetRepository.save(valuesetEntity3);
+ }
+
+ @Test
+ void testGetValuesetIds() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/valuesets")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.length()").value(equalTo(3)))
+ .andExpect(jsonPath("$[0]").value(equalTo(valuesetEntity1.getId())))
+ .andExpect(jsonPath("$[1]").value(equalTo(valuesetEntity2.getId())))
+ .andExpect(jsonPath("$[2]").value(equalTo(valuesetEntity3.getId())));
+ }
+
+ @Test
+ void testGetValueset() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/valuesets/" + valuesetEntity1.getId())
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.key1").value(equalTo("content1")));
+
+ mockMvc.perform(get("/valuesets/" + valuesetEntity2.getId())
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.key2").value(equalTo("content2")));
+
+ mockMvc.perform(get("/valuesets/" + valuesetEntity3.getId())
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.key3").value(equalTo("content3")));
+ }
+
+ @Test
+ void testGetValuesetNotFound() throws Exception {
+ String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(get("/valuesets/randomId")
+ .accept(MediaType.APPLICATION_JSON_VALUE)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash)
+ .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject)
+ )
+ .andExpect(status().isNotFound());
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/filter/CertAuthFilterTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/filter/CertAuthFilterTest.java
new file mode 100644
index 00000000..83e1c195
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/filter/CertAuthFilterTest.java
@@ -0,0 +1,213 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.restapi.filter;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class CertAuthFilterTest {
+
+ @Autowired
+ private DgcConfigProperties properties;
+
+ @Autowired
+ private TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ MockMvc mockMvc;
+
+ private final String countryCode = "EU";
+ private final String authDn = "C=" + countryCode;
+
+ @Test
+ void testRequestShouldFailIfDNHeaderIsMissing() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), certHash)
+ ).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ void testRequestShouldFailIfThumbprintHeaderIsMissing() throws Exception {
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), authDn)
+ ).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ void testRequestShouldFailIfCertHeadersAreMissing() throws Exception {
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ ).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ void testRequestShouldFailIfCertIsNotOnWhitelist() throws Exception {
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), "randomString")
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), authDn)
+ ).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ void testFilterShouldAppendCountryAndThumbprintToRequestObject() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), certHash)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), authDn)
+ ).andExpect(mvcResult -> {
+ Assertions.assertEquals("EU", mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY));
+ Assertions.assertEquals(
+ certHash,
+ mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_THUMBPRINT)
+ );
+ });
+ }
+
+ @Test
+ void testFilterShouldDecodeDnString() throws Exception {
+ String encodedDnString = "ST%3dSome-State%2c%20C%3dEU%2c%20O%3dInternet%20Widgits%20Pty%20Ltd%2c%20CN%3dTest%20Cert";
+
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), certHash)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), encodedDnString)
+ ).andExpect(mvcResult -> {
+ Assertions.assertEquals("EU", mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY));
+ Assertions.assertEquals(
+ certHash,
+ mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_THUMBPRINT)
+ );
+ });
+ }
+
+ @Test
+ void testFilterShouldDecodeBase64AndUrlEncodedCertThumbprint() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+ byte[] certHashBytes = new BigInteger(certHash, 16).toByteArray();
+
+ if (certHashBytes[0] == 0) {
+ byte[] truncatedCertHashBytes = new byte[certHashBytes.length - 1];
+ System.arraycopy(certHashBytes, 1, truncatedCertHashBytes, 0, truncatedCertHashBytes.length);
+ certHashBytes = truncatedCertHashBytes;
+ }
+
+ String encodedThumbprint =
+ URLEncoder.encode(Base64.getEncoder().encodeToString(certHashBytes), StandardCharsets.UTF_8);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), encodedThumbprint)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), "O=Test Firma GmbH,C=EU,U=,TR,TT=43")
+ ).andExpect(mvcResult -> {
+ Assertions.assertEquals("EU", mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY));
+ Assertions.assertEquals(
+ certHash,
+ mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_THUMBPRINT)
+ );
+ });
+ }
+
+ @Test
+ void testFilterShouldDecodeBase64EncodedCertThumbprint() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+ byte[] certHashBytes = new BigInteger(certHash, 16).toByteArray();
+
+ if (certHashBytes[0] == 0) {
+ byte[] truncatedCertHashBytes = new byte[certHashBytes.length - 1];
+ System.arraycopy(certHashBytes, 1, truncatedCertHashBytes, 0, truncatedCertHashBytes.length);
+ certHashBytes = truncatedCertHashBytes;
+ }
+
+ String encodedThumbprint = Base64.getEncoder().encodeToString(certHashBytes);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), encodedThumbprint)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), "O=Test Firma GmbH,C=EU,U=,TR,TT=43")
+ ).andExpect(mvcResult -> {
+ Assertions.assertEquals("EU", mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY));
+ Assertions.assertEquals(
+ certHash,
+ mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_THUMBPRINT)
+ );
+ });
+ }
+
+
+ @Test
+ void testRequestShouldFailIfCountryIsNotPresentInDnString() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), certHash)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), "O=Test Firma GmbH,U=Abteilung XYZ,TR=test")
+ ).andExpect(status().isBadRequest());
+ }
+
+ @Test
+ void testFilterShouldFindCountryEvenOnMalformedDnString() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), certHash)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), "O=Test Firma GmbH,C=EU,U=,TR,TT=43")
+ ).andExpect(mvcResult -> Assertions.assertEquals("EU", mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY)));
+ }
+
+ @Test
+ void testRequestShouldNotFailIfDnStringContainsDuplicatedKeys() throws Exception {
+ String certHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+
+ mockMvc.perform(post("/signerCertificate/")
+ .contentType("application/cms")
+ .header(properties.getCertAuth().getHeaderFields().getThumbprint(), certHash)
+ .header(properties.getCertAuth().getHeaderFields().getDistinguishedName(), "O=Test Firma GmbH,O=XXX,C=EU,U=Abteilung XYZ,TR=test")
+ ).andExpect(mvcResult -> Assertions.assertEquals("EU", mvcResult.getRequest().getAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY)));
+ }
+}
+
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/AuditServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/AuditServiceTest.java
new file mode 100644
index 00000000..35e4ebcd
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/service/AuditServiceTest.java
@@ -0,0 +1,93 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.service;
+
+import eu.europa.ec.dgc.gateway.entity.AuditEventEntity;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.AuditEventRepository;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class AuditServiceTest {
+
+ @Autowired
+ AuditService auditService;
+
+ @Autowired
+ AuditEventRepository auditEventRepository;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ private static final String countryCode = "EU";
+ private static final String dummySignature = "randomStringAsSignatureWhichIsNotValidatedInServiceLevel";
+
+ @Test
+ void testSuccessfulCreateAuditEvent() {
+ auditEventRepository.deleteAll();
+ String exampleEvent = "postVerificationInformation_ALREADY_EXIST_CHECK_FAILED";
+ String exampleEventDescription = "ALREADY_EXIST_CHECK_FAILED";
+
+ auditService.addAuditEvent(countryCode, dummySignature,
+ dummySignature, exampleEvent, exampleEventDescription);
+
+ Assertions.assertEquals(1, auditEventRepository.count());
+ AuditEventEntity auditEvent = auditEventRepository.findAll().get(0);
+
+ Assertions.assertEquals(countryCode, auditEvent.getCountry());
+ Assertions.assertEquals(dummySignature, auditEvent.getAuthenticationSha256Fingerprint());
+ Assertions.assertEquals(dummySignature, auditEvent.getUploaderSha256Fingerprint());
+ Assertions.assertEquals(exampleEvent, auditEvent.getEvent());
+ Assertions.assertEquals(exampleEventDescription, auditEvent.getDescription());
+ }
+
+ @Test
+ void testSuccessfulCreateAuditEventWithCertificate() throws Exception {
+ auditEventRepository.deleteAll();
+ String exampleEvent = "postVerificationInformation_ALREADY_EXIST_CHECK_FAILED";
+ String exampleEventDescription = "ALREADY_EXIST_CHECK_FAILED";
+
+ auditService.addAuditEvent(
+ countryCode,
+ certificateUtils.convertCertificate(
+ trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode)),
+ dummySignature,
+ exampleEvent,
+ exampleEventDescription);
+
+ Assertions.assertEquals(1, auditEventRepository.count());
+ AuditEventEntity auditEvent = auditEventRepository.findAll().get(0);
+
+ Assertions.assertEquals(countryCode, auditEvent.getCountry());
+ Assertions.assertEquals(dummySignature, auditEvent.getAuthenticationSha256Fingerprint());
+ Assertions.assertEquals(trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.UPLOAD, countryCode), auditEvent.getUploaderSha256Fingerprint());
+ Assertions.assertEquals(exampleEvent, auditEvent.getEvent());
+ Assertions.assertEquals(exampleEventDescription, auditEvent.getDescription());
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/RatValuesetUpdateServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/RatValuesetUpdateServiceTest.java
new file mode 100644
index 00000000..fc73b5e6
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/service/RatValuesetUpdateServiceTest.java
@@ -0,0 +1,475 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.service;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import eu.europa.ec.dgc.gateway.client.JrcClient;
+import eu.europa.ec.dgc.gateway.entity.ValuesetEntity;
+import eu.europa.ec.dgc.gateway.model.JrcRatValueset;
+import eu.europa.ec.dgc.gateway.model.JrcRatValuesetResponse;
+import eu.europa.ec.dgc.gateway.model.RatValueset;
+import eu.europa.ec.dgc.gateway.model.Valueset;
+import eu.europa.ec.dgc.gateway.repository.ValuesetRepository;
+import feign.FeignException;
+import feign.Request;
+import feign.RequestTemplate;
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+@SpringBootTest
+class RatValuesetUpdateServiceTest {
+
+ @MockBean
+ JrcClient jrcClientMock;
+
+ @Autowired
+ RatValuesetUpdateService ratValuesetUpdateService;
+
+ @Autowired
+ ValuesetRepository valuesetRepository;
+
+ @Autowired
+ ValuesetService valuesetService;
+
+ @Autowired
+ ObjectMapper objectMapper;
+
+ private static final String RAT_VALUESET_ID = "covid-19-lab-test-manufacturer-and-name";
+
+ private RatValueset rat1, rat2;
+ private final static String RAT1_ID = "1234";
+ private final static String RAT2_ID = "5678";
+ private ValuesetEntity otherValuesetEntity, valuesetEntity;
+ private Valueset valueset;
+ private static final TypeReference> typeReference = new TypeReference<>() {
+ };
+ public static final Request dummyRequest =
+ Request.create(Request.HttpMethod.GET, "url", new HashMap<>(), null, new RequestTemplate());
+
+ @BeforeEach
+ void setup() throws JsonProcessingException {
+ valuesetRepository.deleteAll();
+
+ // Create a dummy valueset which should not be touched
+ otherValuesetEntity = new ValuesetEntity(
+ "other-valueset-with-different-id",
+ "this-should-not-be-changes"
+ );
+ otherValuesetEntity = valuesetRepository.save(otherValuesetEntity);
+
+ // Create a RAT valueset which should be updated
+ rat1 = new RatValueset();
+ rat1.setActive(true);
+ rat1.setVersion(ZonedDateTime.now().minus(5, ChronoUnit.DAYS));
+ rat1.setDisplay("RAT 1");
+
+ rat2 = new RatValueset();
+ rat2.setActive(true);
+ rat2.setVersion(ZonedDateTime.now().minus(6, ChronoUnit.DAYS));
+ rat2.setDisplay("RAT 2");
+
+ valueset = new Valueset<>(
+ RAT_VALUESET_ID,
+ LocalDate.now().minus(1, ChronoUnit.DAYS),
+ Map.of(
+ RAT1_ID, rat1,
+ RAT2_ID, rat2
+ )
+ );
+
+ valuesetEntity = new ValuesetEntity(
+ RAT_VALUESET_ID,
+ objectMapper.writeValueAsString(valueset)
+ );
+ valuesetEntity = valuesetRepository.save(valuesetEntity);
+ }
+
+ @Test
+ void testRatValuesetUpdateActiveFalse() throws JsonProcessingException {
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset.HscListHistory history1 = new JrcRatValueset.HscListHistory();
+ history1.setInCommonList(true);
+ history1.setInMutualRecognition(true);
+ history1.setListDate(ZonedDateTime.now().minus(3, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history2 = new JrcRatValueset.HscListHistory();
+ history2.setInCommonList(false);
+ history2.setInMutualRecognition(true);
+ history2.setListDate(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(List.of(history1, history2));
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not updated.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size(), "Valueset List size has been changed");
+ Assertions.assertFalse(updatedValueset.getValue().get(RAT1_ID).getActive());
+ Assertions.assertEquals(String.format("%s, %s", manufacturer.getName(), jrcValueset.getCommercialName()), updatedValueset.getValue().get(RAT1_ID).getDisplay());
+ Assertions.assertEquals(history2.getListDate().toEpochSecond(), updatedValueset.getValue().get(RAT1_ID).getVersion().toEpochSecond());
+ }
+
+ @Test
+ void testRatValuesetUpdateActiveTrue() throws JsonProcessingException {
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset.HscListHistory history1 = new JrcRatValueset.HscListHistory();
+ history1.setInCommonList(false);
+ history1.setInMutualRecognition(true);
+ history1.setListDate(ZonedDateTime.now().minus(3, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history2 = new JrcRatValueset.HscListHistory();
+ history2.setInCommonList(true);
+ history2.setInMutualRecognition(true);
+ history2.setListDate(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(List.of(history1, history2));
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not updated.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size(), "Valueset List size has been changed");
+ Assertions.assertTrue(updatedValueset.getValue().get(RAT1_ID).getActive());
+ Assertions.assertEquals(String.format("%s, %s", manufacturer.getName(), jrcValueset.getCommercialName()), updatedValueset.getValue().get(RAT1_ID).getDisplay());
+ Assertions.assertEquals(history2.getListDate().toEpochSecond(), updatedValueset.getValue().get(RAT1_ID).getVersion().toEpochSecond());
+ }
+
+ @Test
+ void testRatValuesetInsertedIfNotExist() throws JsonProcessingException {
+ valuesetRepository.deleteAll();
+
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset.HscListHistory history1 = new JrcRatValueset.HscListHistory();
+ history1.setInCommonList(false);
+ history1.setInMutualRecognition(true);
+ history1.setListDate(ZonedDateTime.now().minus(3, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history2 = new JrcRatValueset.HscListHistory();
+ history2.setInCommonList(true);
+ history2.setInMutualRecognition(true);
+ history2.setListDate(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(List.of(history1, history2));
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not updated.");
+ Assertions.assertEquals(1, updatedValueset.getValue().size(), "Valueset List size has been changed");
+ Assertions.assertTrue(updatedValueset.getValue().get(RAT1_ID).getActive());
+ Assertions.assertEquals(String.format("%s, %s", manufacturer.getName(), jrcValueset.getCommercialName()), updatedValueset.getValue().get(RAT1_ID).getDisplay());
+ Assertions.assertEquals(history2.getListDate().toEpochSecond(), updatedValueset.getValue().get(RAT1_ID).getVersion().toEpochSecond());
+ }
+
+ @Test
+ void testRatValuesetUpdatedIfJsonInDbIsInvalid() throws JsonProcessingException {
+ valuesetEntity.setJson("blablabla");
+ valuesetRepository.save(valuesetEntity);
+
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset.HscListHistory history1 = new JrcRatValueset.HscListHistory();
+ history1.setInCommonList(false);
+ history1.setInMutualRecognition(true);
+ history1.setListDate(ZonedDateTime.now().minus(3, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history2 = new JrcRatValueset.HscListHistory();
+ history2.setInCommonList(true);
+ history2.setInMutualRecognition(true);
+ history2.setListDate(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(List.of(history1, history2));
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not set.");
+ Assertions.assertEquals(1, updatedValueset.getValue().size());
+ Assertions.assertTrue(updatedValueset.getValue().get(RAT1_ID).getActive());
+ Assertions.assertEquals(String.format("%s, %s", manufacturer.getName(), jrcValueset.getCommercialName()), updatedValueset.getValue().get(RAT1_ID).getDisplay());
+ Assertions.assertEquals(history2.getListDate().toEpochSecond(), updatedValueset.getValue().get(RAT1_ID).getVersion().toEpochSecond());
+ }
+
+ @Test
+ void testRatValuesetUpdatedSkipIfHistoryEmpty() throws JsonProcessingException {
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(Collections.emptyList());
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not set.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size());
+ Assertions.assertTrue(updatedValueset.getValue().get(RAT1_ID).getActive());
+ assertEquals(rat1, updatedValueset.getValue().get(RAT1_ID));
+ }
+
+ @Test
+ void testRatValuesetUpdatedSkipIfHistoryNull() throws JsonProcessingException {
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(null);
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not set.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size());
+ Assertions.assertTrue(updatedValueset.getValue().get(RAT1_ID).getActive());
+ assertEquals(rat1, updatedValueset.getValue().get(RAT1_ID));
+ }
+
+ @Test
+ void testRatValuesetUpdateShouldNotUpdateWhenRequestFails() throws JsonProcessingException {
+
+ doThrow(new FeignException.Unauthorized("", dummyRequest, null))
+ .when(jrcClientMock).downloadRatValues();
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now().minus(1, ChronoUnit.DAYS), updatedValueset.getDate(), "Valueset Date has been updated.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size(), "Valueset List size has been changed");
+ assertEquals(rat1, updatedValueset.getValue().get(RAT1_ID));
+ assertEquals(rat2, updatedValueset.getValue().get(RAT2_ID));
+ }
+
+ @Test
+ void testRatValuesetUpdateLatestAllHistoryEntriesAreInFuture() throws JsonProcessingException {
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset.HscListHistory history1 = new JrcRatValueset.HscListHistory();
+ history1.setInCommonList(false);
+ history1.setInMutualRecognition(true);
+ history1.setListDate(ZonedDateTime.now().plus(3, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history2 = new JrcRatValueset.HscListHistory();
+ history2.setInCommonList(true);
+ history2.setInMutualRecognition(true);
+ history2.setListDate(ZonedDateTime.now().plus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(List.of(history1, history2));
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not updated.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size(), "Valueset List size has been changed");
+ Assertions.assertNull(updatedValueset.getValue().get(RAT1_ID).getActive());
+ Assertions.assertEquals(String.format("%s, %s", manufacturer.getName(), jrcValueset.getCommercialName()), updatedValueset.getValue().get(RAT1_ID).getDisplay());
+ Assertions.assertNull(updatedValueset.getValue().get(RAT1_ID).getVersion());
+ assertEquals(history1.getListDate(), updatedValueset.getValue().get(RAT1_ID).getValidUntil());
+ }
+
+ @Test
+ void testRatValuesetUpdateLatestHistoryEntryNotInFuture() throws JsonProcessingException {
+ JrcRatValueset.Manufacturer manufacturer = new JrcRatValueset.Manufacturer();
+ manufacturer.setId("1111");
+ manufacturer.setCountry("eu");
+ manufacturer.setName("Manufacturer Name");
+ manufacturer.setWebsite("https://example.org");
+
+ JrcRatValueset.HscListHistory history1 = new JrcRatValueset.HscListHistory();
+ history1.setInCommonList(false);
+ history1.setInMutualRecognition(true);
+ history1.setListDate(ZonedDateTime.now().minus(3, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history2 = new JrcRatValueset.HscListHistory();
+ history2.setInCommonList(true);
+ history2.setInMutualRecognition(true);
+ history2.setListDate(ZonedDateTime.now().minus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset.HscListHistory history3 = new JrcRatValueset.HscListHistory();
+ history3.setInCommonList(true);
+ history3.setInMutualRecognition(true);
+ history3.setListDate(ZonedDateTime.now().plus(1, ChronoUnit.DAYS));
+
+ JrcRatValueset jrcValueset = new JrcRatValueset();
+ jrcValueset.setIdDevice(RAT1_ID);
+ jrcValueset.setCommercialName("New Com Name");
+ jrcValueset.setManufacturer(manufacturer);
+ jrcValueset.setHscListHistory(List.of(history1, history2, history3));
+
+ JrcRatValuesetResponse jrcResponse = new JrcRatValuesetResponse();
+ jrcResponse.setExtractedOn(ZonedDateTime.now());
+ jrcResponse.setDeviceList(List.of(jrcValueset));
+
+ when(jrcClientMock.downloadRatValues()).thenReturn(jrcResponse);
+
+ ratValuesetUpdateService.update();
+
+ String updatedValuesetJson = valuesetService.getValueSetById(RAT_VALUESET_ID).orElseThrow();
+ Valueset updatedValueset = objectMapper.readValue(updatedValuesetJson, typeReference);
+
+ Assertions.assertEquals(LocalDate.now(), updatedValueset.getDate(), "Valueset Date was not updated.");
+ Assertions.assertEquals(2, updatedValueset.getValue().size(), "Valueset List size has been changed");
+ Assertions.assertEquals(history2.getInCommonList(), updatedValueset.getValue().get(RAT1_ID).getActive());
+ Assertions.assertEquals(String.format("%s, %s", manufacturer.getName(), jrcValueset.getCommercialName()), updatedValueset.getValue().get(RAT1_ID).getDisplay());
+ assertEquals(history2.getListDate(), updatedValueset.getValue().get(RAT1_ID).getVersion());
+ assertEquals(history3.getListDate(), updatedValueset.getValue().get(RAT1_ID).getValidUntil());
+ }
+
+ void assertEquals(ZonedDateTime expected, ZonedDateTime given) {
+ Assertions.assertEquals(expected.toEpochSecond(), given.toEpochSecond());
+ }
+
+ void assertEquals(RatValueset expected, RatValueset given) {
+ Assertions.assertEquals(expected.getVersion().toEpochSecond(), given.getVersion().toEpochSecond());
+ Assertions.assertEquals(expected.getActive(), given.getActive());
+ Assertions.assertEquals(expected.getDisplay(), given.getDisplay());
+ Assertions.assertEquals(expected.getLang(), given.getLang());
+ Assertions.assertEquals(expected.getSystem(), given.getSystem());
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java
new file mode 100644
index 00000000..dc134e03
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java
@@ -0,0 +1,360 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.service;
+
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository;
+import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils;
+import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Optional;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SignerInformationServiceTest {
+
+ @Autowired
+ DgcConfigProperties dgcConfigProperties;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ @Autowired
+ DgcTestKeyStore dgcTestKeyStore;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ SignerInformationRepository signerInformationRepository;
+
+ @Autowired
+ SignerInformationService signerInformationService;
+
+ private static final String countryCode = "EU";
+ private static final String dummySignature = "randomStringAsSignatureWhichIsNotValidatedInServiceLevel";
+
+ @Test
+ void testSuccessfulAddingNewSignerInformationAndDelete() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+
+ Assertions.assertEquals(signerInformationEntitiesInDb + 1, signerInformationRepository.count());
+ Optional createdSignerInformationEntity =
+ signerInformationRepository.getFirstByThumbprint(certificateUtils.getCertThumbprint(payloadCertificate));
+
+ Assertions.assertTrue(createdSignerInformationEntity.isPresent());
+
+ Assertions.assertEquals(SignerInformationEntity.CertificateType.DSC, createdSignerInformationEntity.get().getCertificateType());
+ Assertions.assertEquals(countryCode, createdSignerInformationEntity.get().getCountry());
+ Assertions.assertEquals(dummySignature, createdSignerInformationEntity.get().getSignature());
+ Assertions.assertEquals(Base64.getEncoder().encodeToString(payloadCertificate.getEncoded()), createdSignerInformationEntity.get().getRawData());
+
+ signerInformationService.deleteSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ countryCode
+ );
+
+ Optional deletedSignerInformationEntity =
+ signerInformationRepository.getFirstByThumbprint(certificateUtils.getCertThumbprint(payloadCertificate));
+
+ Assertions.assertTrue(deletedSignerInformationEntity.isEmpty());
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testAddingFailedConflict() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+
+ try {
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.ALREADY_EXIST_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb + 1, signerInformationRepository.count());
+ }
+
+ @Test
+ void testAddingFailedKidConflict() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+
+ Optional certInDbOptional = signerInformationRepository.getFirstByThumbprint(certificateUtils.getCertThumbprint(payloadCertificate));
+
+ Assertions.assertTrue(certInDbOptional.isPresent());
+
+ SignerInformationEntity certInDb = certInDbOptional.get();
+ certInDb.setThumbprint(certInDb.getThumbprint().substring(0, 40) + "x".repeat(24)); // Generate new Hash with first 40 chars from ogirinal hash and add 24 x
+
+ signerInformationRepository.save(certInDb);
+
+ try {
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.KID_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb + 1, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedInvalidCSCA() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ // sign with TrustAnchor
+ X509Certificate cscaCertificate = dgcTestKeyStore.getTrustAnchor();
+ PrivateKey cscaPrivateKey = dgcTestKeyStore.getTrustAnchorPrivateKey();
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.CSCA_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedInvalidCSCAWrongCountryCode() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ // sign with CSCA from another country
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "XX");
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, "XX");
+
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.CSCA_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedPayloadCertCountryWrong() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, "XX", "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.COUNTRY_OF_ORIGIN_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testUploadFailedWrongSignerCertificate() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "XX");
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.addSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ dummySignature,
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.UPLOADER_CERT_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testDeleteFailedNotExists() throws Exception {
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.deleteSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.EXIST_CHECK_FAILED, e.getReason());
+ }
+ }
+
+ @Test
+ void testDeleteFailedPayloadCertCountryWrong() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, "XX", "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.deleteSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.COUNTRY_OF_ORIGIN_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+
+ @Test
+ void testDeleteFailedWrongSignerCertificate() throws Exception {
+ long signerInformationEntitiesInDb = signerInformationRepository.count();
+
+ X509Certificate signerCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "XX");
+
+ X509Certificate cscaCertificate = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ PrivateKey cscaPrivateKey = trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+
+ KeyPair payloadKeyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate payloadCertificate = CertificateTestUtils.generateCertificate(payloadKeyPair, countryCode, "Payload Cert", cscaCertificate, cscaPrivateKey);
+
+ try {
+ signerInformationService.deleteSignerCertificate(
+ new X509CertificateHolder(payloadCertificate.getEncoded()),
+ new X509CertificateHolder(signerCertificate.getEncoded()),
+ countryCode
+ );
+ } catch (SignerInformationService.SignerCertCheckException e) {
+ Assertions.assertEquals(SignerInformationService.SignerCertCheckException.Reason.UPLOADER_CERT_CHECK_FAILED, e.getReason());
+ }
+
+ Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count());
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java
new file mode 100644
index 00000000..c75f6452
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java
@@ -0,0 +1,193 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.service;
+
+import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity;
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.model.TrustList;
+import eu.europa.ec.dgc.gateway.model.TrustListType;
+import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository;
+import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository;
+import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.security.KeyPairGenerator;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.time.ZonedDateTime;
+import java.util.Base64;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class TrustListServiceTest {
+
+ @Autowired
+ SignerInformationRepository signerInformationRepository;
+
+ @Autowired
+ TrustedPartyRepository trustedPartyRepository;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ TrustListService trustListService;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ X509Certificate certUploadDe, certUploadEu, certCscaDe, certCscaEu, certAuthDe, certAuthEu, certDscDe, certDscEu;
+
+ @BeforeEach
+ void testData() throws Exception {
+ trustedPartyRepository.deleteAll();
+ signerInformationRepository.deleteAll();
+
+ certUploadDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "DE");
+ certUploadEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "EU");
+ certCscaDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "DE");
+ certCscaEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, "EU");
+ certAuthDe = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE");
+ certAuthEu = trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU");
+
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec");
+ certDscDe = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "Test");
+ certDscEu = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EU", "Test");
+
+ signerInformationRepository.save(new SignerInformationEntity(
+ null,
+ ZonedDateTime.now(),
+ "DE",
+ certificateUtils.getCertThumbprint(certDscDe),
+ Base64.getEncoder().encodeToString(certDscDe.getEncoded()),
+ "sig1",
+ SignerInformationEntity.CertificateType.DSC
+ ));
+
+ signerInformationRepository.save(new SignerInformationEntity(
+ null,
+ ZonedDateTime.now(),
+ "EU",
+ certificateUtils.getCertThumbprint(certDscEu),
+ Base64.getEncoder().encodeToString(certDscEu.getEncoded()),
+ "sig2",
+ SignerInformationEntity.CertificateType.DSC
+ ));
+ }
+
+ @Test
+ void testTrustListWithoutFilter() throws Exception {
+ List trustList = trustListService.getTrustList();
+
+ Assertions.assertEquals(8, trustList.size());
+
+ assertTrustListItem(trustList, certDscDe, "DE", TrustListType.DSC, "sig1");
+ assertTrustListItem(trustList, certDscEu, "EU", TrustListType.DSC, "sig2");
+ assertTrustListItem(trustList, certCscaDe, "DE", TrustListType.CSCA, null);
+ assertTrustListItem(trustList, certCscaEu, "EU", TrustListType.CSCA, null);
+ assertTrustListItem(trustList, certUploadDe, "DE", TrustListType.UPLOAD, null);
+ assertTrustListItem(trustList, certUploadEu, "EU", TrustListType.UPLOAD, null);
+ assertTrustListItem(trustList, certAuthDe, "DE", TrustListType.AUTHENTICATION, null);
+ assertTrustListItem(trustList, certAuthEu, "EU", TrustListType.AUTHENTICATION, null);
+ }
+
+ @Test
+ void testTrustListFilterByType() throws Exception {
+ List trustList = trustListService.getTrustList(TrustListType.DSC);
+ Assertions.assertEquals(2, trustList.size());
+ assertTrustListItem(trustList, certDscDe, "DE", TrustListType.DSC, "sig1");
+ assertTrustListItem(trustList, certDscEu, "EU", TrustListType.DSC, "sig2");
+
+ trustList = trustListService.getTrustList(TrustListType.CSCA);
+ Assertions.assertEquals(2, trustList.size());
+ assertTrustListItem(trustList, certCscaDe, "DE", TrustListType.CSCA, null);
+ assertTrustListItem(trustList, certCscaEu, "EU", TrustListType.CSCA, null);
+
+ trustList = trustListService.getTrustList(TrustListType.UPLOAD);
+ Assertions.assertEquals(2, trustList.size());
+ assertTrustListItem(trustList, certUploadDe, "DE", TrustListType.UPLOAD, null);
+ assertTrustListItem(trustList, certUploadEu, "EU", TrustListType.UPLOAD, null);
+
+ trustList = trustListService.getTrustList(TrustListType.AUTHENTICATION);
+ Assertions.assertEquals(2, trustList.size());
+ assertTrustListItem(trustList, certAuthDe, "DE", TrustListType.AUTHENTICATION, null);
+ assertTrustListItem(trustList, certAuthEu, "EU", TrustListType.AUTHENTICATION, null);
+ }
+
+ @Test
+ void testTrustListFilterByTypeAndCountry() throws Exception {
+ List trustList = trustListService.getTrustList(TrustListType.DSC, "DE");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certDscDe, "DE", TrustListType.DSC, "sig1");
+ trustList = trustListService.getTrustList(TrustListType.DSC, "EU");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certDscEu, "EU", TrustListType.DSC, "sig2");
+
+ trustList = trustListService.getTrustList(TrustListType.CSCA, "DE");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certCscaDe, "DE", TrustListType.CSCA, null);
+ trustList = trustListService.getTrustList(TrustListType.CSCA, "EU");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certCscaEu, "EU", TrustListType.CSCA, null);
+
+ trustList = trustListService.getTrustList(TrustListType.UPLOAD, "DE");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certUploadDe, "DE", TrustListType.UPLOAD, null);
+ trustList = trustListService.getTrustList(TrustListType.UPLOAD, "EU");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certUploadEu, "EU", TrustListType.UPLOAD, null);
+
+ trustList = trustListService.getTrustList(TrustListType.AUTHENTICATION, "DE");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certAuthDe, "DE", TrustListType.AUTHENTICATION, null);
+ trustList = trustListService.getTrustList(TrustListType.AUTHENTICATION, "EU");
+ Assertions.assertEquals(1, trustList.size());
+ assertTrustListItem(trustList, certAuthEu, "EU", TrustListType.AUTHENTICATION, null);
+ }
+
+ private void assertTrustListItem(List trustList, X509Certificate certificate, String country, TrustListType trustListType, String signature) throws CertificateEncodingException {
+ Optional trustListOptional = trustList
+ .stream()
+ .filter(tl -> tl.getKid().equals(certificateUtils.getCertKid(certificate)))
+ .findFirst();
+
+ Assertions.assertTrue(trustListOptional.isPresent());
+
+ TrustList trustListItem = trustListOptional.get();
+
+ Assertions.assertEquals(certificateUtils.getCertKid(certificate), trustListItem.getKid());
+ Assertions.assertEquals(country, trustListItem.getCountry());
+ Assertions.assertEquals(trustListType, trustListItem.getCertificateType());
+ Assertions.assertEquals(certificateUtils.getCertThumbprint(certificate), trustListItem.getThumbprint());
+ Assertions.assertEquals(Base64.getEncoder().encodeToString(certificate.getEncoded()), trustListItem.getRawData());
+
+ if (signature != null) {
+ Assertions.assertEquals(signature, trustListItem.getSignature());
+ }
+ }
+
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java
new file mode 100644
index 00000000..51c6b28d
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java
@@ -0,0 +1,162 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.service;
+
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository;
+import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder;
+import java.util.Optional;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class TrustedPartyServiceTest {
+
+ @Autowired
+ TrustedPartyRepository trustedPartyRepository;
+
+ @Autowired
+ TrustedPartyService trustedPartyService;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ DgcTestKeyStore dgcTestKeyStore;
+
+ private static final String countryCode = "EU";
+
+ @AfterEach
+ void cleanUp() {
+ // We have to delete all certs after each test because some tests are manipulating certs in DB.
+ trustedPartyRepository.deleteAll();
+ }
+
+ @Test
+ void trustedPartyServiceShouldReturnCertificate() throws Exception {
+ String hash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.UPLOAD, countryCode);
+ Optional certOptional = trustedPartyService.getCertificate(hash, countryCode, TrustedPartyEntity.CertificateType.UPLOAD);
+ Assertions.assertTrue(certOptional.isPresent());
+ Assertions.assertEquals(hash, certOptional.get().getThumbprint());
+
+ hash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode);
+ certOptional = trustedPartyService.getCertificate(hash, countryCode, TrustedPartyEntity.CertificateType.CSCA);
+ Assertions.assertTrue(certOptional.isPresent());
+ Assertions.assertEquals(hash, certOptional.get().getThumbprint());
+
+ hash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode);
+ certOptional = trustedPartyService.getCertificate(hash, countryCode, TrustedPartyEntity.CertificateType.AUTHENTICATION);
+ Assertions.assertTrue(certOptional.isPresent());
+ Assertions.assertEquals(hash, certOptional.get().getThumbprint());
+ }
+
+ @Test
+ void trustedPartyServiceShouldNotReturnCertificateIfIntegrityOfRawDataIsViolated() throws Exception {
+ Optional certOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+
+ Optional anotherCertOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode), countryCode, TrustedPartyEntity.CertificateType.AUTHENTICATION);
+
+ Assertions.assertTrue(certOptional.isPresent());
+ Assertions.assertTrue(anotherCertOptional.isPresent());
+
+ TrustedPartyEntity cert = certOptional.get();
+ cert.setRawData(anotherCertOptional.get().getRawData());
+
+ trustedPartyRepository.save(cert);
+
+ certOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+ Assertions.assertTrue(certOptional.isEmpty());
+ }
+
+ @Test
+ void trustedPartyServiceShouldNotReturnCertificateIfIntegrityOfSignatureIsViolated() throws Exception {
+ Optional certOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+
+ Optional anotherCertOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode), countryCode, TrustedPartyEntity.CertificateType.AUTHENTICATION);
+
+ Assertions.assertTrue(certOptional.isPresent());
+ Assertions.assertTrue(anotherCertOptional.isPresent());
+
+ TrustedPartyEntity cert = certOptional.get();
+ cert.setSignature(anotherCertOptional.get().getSignature());
+
+ trustedPartyRepository.save(cert);
+
+ certOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+ Assertions.assertTrue(certOptional.isEmpty());
+ }
+
+ @Test
+ void trustedPartyServiceShouldNotReturnCertificateIfIntegrityOfThumbprintIsViolated() throws Exception {
+ Optional certOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+
+ Optional anotherCertOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode), countryCode, TrustedPartyEntity.CertificateType.AUTHENTICATION);
+
+ Assertions.assertTrue(certOptional.isPresent());
+ Assertions.assertTrue(anotherCertOptional.isPresent());
+
+ trustedPartyRepository.delete(anotherCertOptional.get());
+
+ TrustedPartyEntity cert = certOptional.get();
+ cert.setThumbprint(anotherCertOptional.get().getThumbprint());
+
+ trustedPartyRepository.save(cert);
+
+ certOptional = trustedPartyService.getCertificate(
+ cert.getThumbprint(), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+ Assertions.assertTrue(certOptional.isEmpty());
+ }
+
+ @Test
+ void trustedPartyServiceShouldNotReturnCertificateIfSignatureIsFromUnknownTrustAnchor() throws Exception {
+ Optional certOptional = trustedPartyService.getCertificate(
+ trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.CSCA, countryCode), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+
+ // Create new signature with a random non TrustAnchor certificate
+ String newSignature = new SignedCertificateMessageBuilder()
+ .withSigningCertificate(new X509CertificateHolder(trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.UPLOAD, "XX").getEncoded()), trustedPartyTestHelper.getPrivateKey(TrustedPartyEntity.CertificateType.UPLOAD, "XX"))
+ .withPayload(new X509CertificateHolder(trustedPartyTestHelper.getCert(TrustedPartyEntity.CertificateType.CSCA, countryCode).getEncoded()))
+ .buildAsString(true);
+
+ Assertions.assertTrue(certOptional.isPresent());
+
+ TrustedPartyEntity trustedPartyEntity = certOptional.get();
+ trustedPartyEntity.setSignature(newSignature);
+ trustedPartyRepository.save(trustedPartyEntity);
+
+ certOptional = trustedPartyService.getCertificate(trustedPartyEntity.getThumbprint(), countryCode, TrustedPartyEntity.CertificateType.CSCA);
+ Assertions.assertTrue(certOptional.isEmpty());
+ }
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/ValuesetServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/ValuesetServiceTest.java
new file mode 100644
index 00000000..e5883bde
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/service/ValuesetServiceTest.java
@@ -0,0 +1,85 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.service;
+
+import eu.europa.ec.dgc.gateway.entity.ValuesetEntity;
+import eu.europa.ec.dgc.gateway.repository.ValuesetRepository;
+import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ValuesetServiceTest {
+
+ @Autowired
+ ValuesetService valuesetService;
+
+ @Autowired
+ ValuesetRepository valuesetRepository;
+
+ @Autowired
+ TrustedPartyTestHelper trustedPartyTestHelper;
+
+ @Autowired
+ CertificateUtils certificateUtils;
+
+ @BeforeEach
+ void setup() {
+ valuesetRepository.deleteAll();
+ }
+
+ @Test
+ void testGetValuesetIds() {
+ ValuesetEntity valuesetEntity1 = new ValuesetEntity("vs-dummy-1", "content1");
+ ValuesetEntity valuesetEntity2 = new ValuesetEntity("vs-dummy-2", "content2");
+ ValuesetEntity valuesetEntity3 = new ValuesetEntity("vs-dummy-3", "content3");
+
+ valuesetRepository.save(valuesetEntity1);
+ valuesetRepository.save(valuesetEntity2);
+ valuesetRepository.save(valuesetEntity3);
+
+
+ List valuesetIds = valuesetService.getValuesetIds();
+ Assertions.assertEquals(3, valuesetService.getValuesetIds().size());
+ Assertions.assertTrue(valuesetIds.contains("vs-dummy-1"));
+ Assertions.assertTrue(valuesetIds.contains("vs-dummy-2"));
+ Assertions.assertTrue(valuesetIds.contains("vs-dummy-3"));
+ }
+
+ @Test
+ void testGetValueset() {
+ ValuesetEntity valuesetEntity1 = new ValuesetEntity("vs-dummy-1", "content1");
+ ValuesetEntity valuesetEntity2 = new ValuesetEntity("vs-dummy-2", "content2");
+
+ valuesetRepository.save(valuesetEntity1);
+ valuesetRepository.save(valuesetEntity2);
+
+ Assertions.assertEquals(valuesetEntity1.getJson(), valuesetService.getValueSetById(valuesetEntity1.getId()).orElseThrow());
+ Assertions.assertEquals(valuesetEntity2.getJson(), valuesetService.getValueSetById(valuesetEntity2.getId()).orElseThrow());
+ }
+
+
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/testdata/CertificateTestUtils.java b/src/test/java/eu/europa/ec/dgc/gateway/testdata/CertificateTestUtils.java
new file mode 100644
index 00000000..63b27c03
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/testdata/CertificateTestUtils.java
@@ -0,0 +1,142 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.testdata;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import eu.europa.ec.dgc.gateway.connector.model.ValidationRule;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.junit.jupiter.api.Assertions;
+
+public class CertificateTestUtils {
+
+ public static ValidationRule getDummyValidationRule() {
+ ValidationRule validationRule = new ValidationRule();
+
+ JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
+
+ validationRule.setLogic(jsonNodeFactory.objectNode().set("field1", jsonNodeFactory.textNode("value1")));
+ validationRule.setValidTo(ZonedDateTime.now().plus(1, ChronoUnit.WEEKS));
+ validationRule.setValidFrom(ZonedDateTime.now().plus(3, ChronoUnit.DAYS));
+ validationRule.setCertificateType("General");
+ validationRule.setDescription(List.of(new ValidationRule.DescriptionItem("en", "de".repeat(10))));
+ validationRule.setEngine("CERTLOGIC");
+ validationRule.setEngineVersion("1.0.0");
+ validationRule.setVersion("1.0.0");
+ validationRule.setAffectedFields(List.of("AB", "DE"));
+ validationRule.setRegion("BW");
+ validationRule.setSchemaVersion("1.0.0");
+ validationRule.setType("Acceptance");
+ validationRule.setIdentifier("GR-EU-0001");
+ validationRule.setCountry("EU");
+
+ return validationRule;
+ }
+
+ public static X509Certificate generateCertificate(KeyPair keyPair, String country, String commonName) throws Exception {
+ Date validFrom = Date.from(Instant.now().minus(1, ChronoUnit.DAYS));
+ Date validTo = Date.from(Instant.now().plus(365, ChronoUnit.DAYS));
+
+ return generateCertificate(keyPair, country, commonName, validFrom, validTo);
+ }
+
+ public static X509Certificate generateCertificate(KeyPair keyPair, String country, String commonName, X509Certificate ca, PrivateKey caKey) throws Exception {
+ Date validFrom = Date.from(Instant.now().minus(1, ChronoUnit.DAYS));
+ Date validTo = Date.from(Instant.now().plus(365, ChronoUnit.DAYS));
+
+ return generateCertificate(keyPair, country, commonName, validFrom, validTo, ca, caKey);
+ }
+
+ public static X509Certificate generateCertificate(KeyPair keyPair, String country, String commonName, Date validFrom, Date validTo) throws Exception {
+ X500Name subject = new X500NameBuilder()
+ .addRDN(X509ObjectIdentifiers.countryName, country)
+ .addRDN(X509ObjectIdentifiers.commonName, commonName)
+ .build();
+
+ BigInteger certSerial = new BigInteger(Long.toString(System.currentTimeMillis()));
+
+ ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(keyPair.getPrivate());
+
+ JcaX509v3CertificateBuilder certBuilder =
+ new JcaX509v3CertificateBuilder(subject, certSerial, validFrom, validTo, subject, keyPair.getPublic());
+
+ BasicConstraints basicConstraints = new BasicConstraints(false);
+ certBuilder.addExtension(Extension.basicConstraints, true, basicConstraints);
+
+ return new JcaX509CertificateConverter().getCertificate(certBuilder.build(contentSigner));
+ }
+
+ public static X509Certificate generateCertificate(KeyPair keyPair, String country, String commonName, Date validFrom, Date validTo, X509Certificate ca, PrivateKey caKey) throws Exception {
+ X500Name subject = new X500NameBuilder()
+ .addRDN(X509ObjectIdentifiers.countryName, country)
+ .addRDN(X509ObjectIdentifiers.commonName, commonName)
+ .build();
+
+ X500Name issuer = new X509CertificateHolder(ca.getEncoded()).getSubject();
+
+ BigInteger certSerial = new BigInteger(Long.toString(System.currentTimeMillis()));
+
+ ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(caKey);
+
+ JcaX509v3CertificateBuilder certBuilder =
+ new JcaX509v3CertificateBuilder(issuer, certSerial, validFrom, validTo, subject, keyPair.getPublic());
+
+ BasicConstraints basicConstraints = new BasicConstraints(false);
+ certBuilder.addExtension(Extension.basicConstraints, true, basicConstraints);
+
+ return new JcaX509CertificateConverter().getCertificate(certBuilder.build(contentSigner));
+ }
+
+ public static void assertEquals(ValidationRule v1, ValidationRule v2) {
+ Assertions.assertEquals(v1.getIdentifier(), v2.getIdentifier());
+ Assertions.assertEquals(v1.getType(), v2.getType());
+ Assertions.assertEquals(v1.getCountry(), v2.getCountry());
+ Assertions.assertEquals(v1.getRegion(), v2.getRegion());
+ Assertions.assertEquals(v1.getVersion(), v2.getVersion());
+ Assertions.assertEquals(v1.getSchemaVersion(), v2.getSchemaVersion());
+ Assertions.assertEquals(v1.getEngine(), v2.getEngine());
+ Assertions.assertEquals(v1.getEngineVersion(), v2.getEngineVersion());
+ Assertions.assertEquals(v1.getCertificateType(), v2.getCertificateType());
+ Assertions.assertEquals(v1.getDescription(), v2.getDescription());
+ Assertions.assertEquals(v1.getValidFrom().toEpochSecond(), v2.getValidFrom().toEpochSecond());
+ Assertions.assertEquals(v1.getValidTo().toEpochSecond(), v2.getValidTo().toEpochSecond());
+ Assertions.assertEquals(v1.getAffectedFields(), v2.getAffectedFields());
+ Assertions.assertEquals(v1.getLogic(), v2.getLogic());
+ }
+
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/testdata/DgcTestKeyStore.java b/src/test/java/eu/europa/ec/dgc/gateway/testdata/DgcTestKeyStore.java
new file mode 100644
index 00000000..330865d4
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/testdata/DgcTestKeyStore.java
@@ -0,0 +1,79 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.testdata;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import eu.europa.ec.dgc.gateway.config.DgcConfigProperties;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import lombok.Getter;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+@TestConfiguration
+public class DgcTestKeyStore {
+
+ private final DgcConfigProperties configProperties;
+
+ @Getter
+ private final X509Certificate trustAnchor;
+
+ @Getter
+ private final PrivateKey trustAnchorPrivateKey;
+
+ public DgcTestKeyStore(DgcConfigProperties configProperties) throws Exception {
+ this.configProperties = configProperties;
+
+ KeyPair keyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ trustAnchorPrivateKey = keyPair.getPrivate();
+
+ trustAnchor = CertificateTestUtils.generateCertificate(keyPair, "DE", "DGCG Test TrustAnchor");
+
+ }
+
+ /**
+ * Creates a KeyStore instance with keys for DGC.
+ */
+ @Bean
+ @Primary
+ public KeyStore testKeyStore() throws IOException, CertificateException, NoSuchAlgorithmException {
+ KeyStoreSpi keyStoreSpiMock = mock(KeyStoreSpi.class);
+ KeyStore keyStoreMock = new KeyStore(keyStoreSpiMock, null, "test") {
+ };
+ keyStoreMock.load(null);
+
+ doAnswer((x) -> trustAnchor)
+ .when(keyStoreSpiMock).engineGetCertificate(configProperties.getTrustAnchor().getCertificateAlias());
+
+ return keyStoreMock;
+ }
+
+}
diff --git a/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java b/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java
new file mode 100644
index 00000000..8b54cffd
--- /dev/null
+++ b/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java
@@ -0,0 +1,126 @@
+/*-
+ * ---license-start
+ * EU Digital Green Certificate Gateway Service / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package eu.europa.ec.dgc.gateway.testdata;
+
+import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity;
+import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository;
+import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder;
+import eu.europa.ec.dgc.utils.CertificateUtils;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class TrustedPartyTestHelper {
+
+ private final Map> hashMap = Map.of(
+ TrustedPartyEntity.CertificateType.AUTHENTICATION, new HashMap<>(),
+ TrustedPartyEntity.CertificateType.CSCA, new HashMap<>(),
+ TrustedPartyEntity.CertificateType.UPLOAD, new HashMap<>()
+ );
+
+ private final Map> certificateMap = Map.of(
+ TrustedPartyEntity.CertificateType.AUTHENTICATION, new HashMap<>(),
+ TrustedPartyEntity.CertificateType.CSCA, new HashMap<>(),
+ TrustedPartyEntity.CertificateType.UPLOAD, new HashMap<>()
+ );
+
+ private final Map> privateKeyMap = Map.of(
+ TrustedPartyEntity.CertificateType.AUTHENTICATION, new HashMap<>(),
+ TrustedPartyEntity.CertificateType.CSCA, new HashMap<>(),
+ TrustedPartyEntity.CertificateType.UPLOAD, new HashMap<>()
+ );
+
+ private final TrustedPartyRepository trustedPartyRepository;
+
+ private final CertificateUtils certificateUtils;
+
+ private final DgcTestKeyStore testKeyStore;
+
+ public String getHash(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception {
+ prepareTestCert(type, countryCode);
+ return hashMap.get(type).get(countryCode);
+ }
+
+ public X509Certificate getCert(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception {
+ prepareTestCert(type, countryCode);
+ return certificateMap.get(type).get(countryCode);
+ }
+
+ public PrivateKey getPrivateKey(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception {
+ prepareTestCert(type, countryCode);
+ return privateKeyMap.get(type).get(countryCode);
+ }
+
+ private void prepareTestCert(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception {
+ // Check if a test certificate already exists
+ if (!hashMap.get(type).containsKey(countryCode)) {
+ createAndInsertCert(type, countryCode);
+ }
+
+ // Check if generated certificate is (still) present in DB
+ if (trustedPartyRepository.getFirstByThumbprintAndCertificateType(
+ hashMap.get(type).get(countryCode), type
+ ).isEmpty()) {
+ insertTestCert(type, countryCode);
+ }
+ }
+
+ private void createAndInsertCert(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception {
+ KeyPair keyPair = KeyPairGenerator.getInstance("ec").generateKeyPair();
+ X509Certificate authCertificate =
+ CertificateTestUtils.generateCertificate(keyPair, countryCode, "DGC Test " + type.name() + " Cert");
+ String certHash = certificateUtils.getCertThumbprint(authCertificate);
+
+ certificateMap.get(type).put(countryCode, authCertificate);
+ hashMap.get(type).put(countryCode, certHash);
+ privateKeyMap.get(type).put(countryCode, keyPair.getPrivate());
+
+ insertTestCert(type, countryCode);
+ }
+
+ private void insertTestCert(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception {
+ String certRawData = Base64.getEncoder().encodeToString(
+ certificateMap.get(type).get(countryCode).getEncoded());
+
+ String signature = new SignedCertificateMessageBuilder()
+ .withPayload(new X509CertificateHolder(certificateMap.get(type).get(countryCode).getEncoded()))
+ .withSigningCertificate(new X509CertificateHolder(testKeyStore.getTrustAnchor().getEncoded()), testKeyStore.getTrustAnchorPrivateKey())
+ .buildAsString(true);
+
+ TrustedPartyEntity trustedPartyEntity = new TrustedPartyEntity();
+ trustedPartyEntity.setCertificateType(type);
+ trustedPartyEntity.setCountry(countryCode);
+ trustedPartyEntity.setSignature(signature);
+ trustedPartyEntity.setRawData(certRawData);
+ trustedPartyEntity.setThumbprint(hashMap.get(type).get(countryCode));
+
+ trustedPartyRepository.save(trustedPartyEntity);
+ }
+}
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
new file mode 100644
index 00000000..ad16a75e
--- /dev/null
+++ b/src/test/resources/application.yml
@@ -0,0 +1,36 @@
+server:
+ port: ${SERVER_PORT:8090}
+spring:
+ profiles:
+ active:
+ - test
+ include:
+ - dev
+ application:
+ name: eu-interop-federation-gateway
+ liquibase:
+ enabled: true
+ change-log: classpath:db/changelog.xml
+ main:
+ allow-bean-definition-overriding: true
+springdoc:
+ api-docs:
+ path: /api/docs
+ swagger-ui:
+ path: /swagger
+
+dgc:
+ jrc:
+ url: https://covid-19-diagnostics.jrc.ec.europa.eu/devices/hsc-common-recognition-rat
+ validationRuleSchema: classpath:validation-rule.schema.json
+ dbencryption:
+ initVector: Ho^RDYDuGt0Ki`\x
+ password: G&B3zSk|fNE!.Pa9+Xv2kUYRx2zp|@=|
+ trustAnchor:
+ keyStorePath: keystore/dgc-ta.jks
+ keyStorePass: dgc-p4ssw0rd
+ certificateAlias: dgc_trust_anchor
+ cert-auth:
+ header-fields:
+ thumbprint: X-SSL-Client-SHA256
+ distinguished-name: X-SSL-Client-DN
diff --git a/templates/file-header.txt b/templates/file-header.txt
new file mode 100644
index 00000000..9af5493d
--- /dev/null
+++ b/templates/file-header.txt
@@ -0,0 +1,19 @@
+/*-
+ * ---license-start
+ * eu-digital-green-certificates / dgc-gateway
+ * ---
+ * Copyright (C) 2021 T-Systems International GmbH and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
diff --git a/templates/third-party.ftl b/templates/third-party.ftl
new file mode 100644
index 00000000..03befca6
--- /dev/null
+++ b/templates/third-party.ftl
@@ -0,0 +1,30 @@
+<#function artifactFormat p>
+ <#return p.groupId + ":" + p.artifactId + ":" + p.version>
+#function>
+<#function licenseFormat licenses>
+ <#assign result = ""/>
+ <#list licenses as license>
+ <#assign result = result + license + " "/>
+ #list>
+ <#return result>
+#function>
+ThirdPartyNotices
+-----------------
+This project uses third-party software or other resources that
+may be distributed under licenses different from this software.
+In the event that we overlooked to list a required notice, please bring this
+to our attention by contacting us via this email:
+opensource@telekom.de
+
+ThirdParty Licenses
+-----------------
+
+| Dependency | License |
+| --- | --- |
+<#compress>
+ <#list dependencyMap as e>
+ <#assign project = e.getKey() />
+ <#assign license = e.getValue() />
+ | ${artifactFormat(project)} | ${licenseFormat(license)} |
+ #list>
+#compress>
\ No newline at end of file