From f3d3114d4d4b4df51e8277daa2a2d2e3da2511d0 Mon Sep 17 00:00:00 2001 From: Frederico Date: Wed, 29 Jul 2020 17:24:06 +0200 Subject: [PATCH 01/28] Revert "Usage of postgres in test environment for database consistency (#686)" (#688) This reverts commit 932a61fb082e1ecddb7b9d88a61fcb08c7a1e32a. --- common/persistence/pom.xml | 8 ++++---- .../db/specific/h2/V2__createPermissions.sql | 1 + .../src/test/resources/application.yaml | 6 ++++-- services/distribution/pom.xml | 6 ------ .../src/test/resources/application.yaml | 14 ++++++++------ services/submission/pom.xml | 6 ------ .../submission/src/test/resources/application.yaml | 8 ++++++-- 7 files changed, 23 insertions(+), 26 deletions(-) create mode 100644 common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index 9dd03b053b..f067ad341e 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -82,10 +82,10 @@ runtime - org.testcontainers - postgresql - 1.14.3 - test + com.h2database + h2 + 1.4.200 + runtime diff --git a/common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql b/common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql new file mode 100644 index 0000000000..2f7af07d54 --- /dev/null +++ b/common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql @@ -0,0 +1 @@ +-- no permissions on H2 necessary diff --git a/common/persistence/src/test/resources/application.yaml b/common/persistence/src/test/resources/application.yaml index f949742728..1dde9bb9cf 100644 --- a/common/persistence/src/test/resources/application.yaml +++ b/common/persistence/src/test/resources/application.yaml @@ -2,9 +2,11 @@ spring: flyway: enabled: true - locations: classpath:/db/migration + locations: classpath:/db/migration, classpath:/db/specific/{vendor} datasource: - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw + url: jdbc:h2:mem:test;MODE=PostgreSQL + driverClassName: org.h2.Driver + platform: h2 test: database: # Use datasource as defined above. diff --git a/services/distribution/pom.xml b/services/distribution/pom.xml index e37632cb75..1ab6d6df30 100644 --- a/services/distribution/pom.xml +++ b/services/distribution/pom.xml @@ -99,12 +99,6 @@ org.springframework.boot spring-boot-starter-aop - - org.testcontainers - postgresql - 1.14.3 - test - diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 346dbf9b74..d779aec0f1 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -55,9 +55,11 @@ services: spring: main: banner-mode: off - flyway: - enabled: true - locations: classpath:db/migration/postgres - jpa: - hibernate: - ddl-auto: validate + datasource: + url: jdbc:h2:mem:test;MODE=PostgreSQL + driverClassName: org.h2.Driver + platform: h2 + test: + database: + # Use datasource as defined above. + replace: none diff --git a/services/submission/pom.xml b/services/submission/pom.xml index 437bbfa3ba..ac670c764b 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -68,12 +68,6 @@ 2.26.3 test - - org.testcontainers - postgresql - 1.14.3 - test - diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 39b2cdd957..dfb4524ff6 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -8,10 +8,14 @@ spring: main: banner-mode: off datasource: - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw + url: jdbc:h2:mem:test;MODE=PostgreSQL + driverClassName: org.h2.Driver + platform: h2 test: database: + # Use datasource as defined above. replace: none + services: submission: initial-fake-delay-milliseconds: 1 @@ -47,4 +51,4 @@ feign: config: default: connect-timeout: 500 - read-timeout: 500 + read-timeout: 500 \ No newline at end of file From ab81402a9fe8b1a8695d73b8e7336a935f333173 Mon Sep 17 00:00:00 2001 From: Sorin Stefan Iovita Date: Thu, 30 Jul 2020 14:57:38 +0300 Subject: [PATCH 02/28] Mitigate CVE-2020-13935 (#690) * Bump spring parent version * Liveness health indicator according to. https://github.com/spring-projects/spring-boot/issues/22107 --- services/pom.xml | 8 +------- services/submission/src/test/resources/application.yaml | 4 +++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/services/pom.xml b/services/pom.xml index b65a1a7adb..507e7bcbe7 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.1.RELEASE + 2.3.2.RELEASE @@ -101,12 +101,6 @@ 42.2.13 runtime - - - org.apache.tomcat.embed - tomcat-embed-core - 9.0.37 - diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index dfb4524ff6..71d9af5252 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -43,6 +43,8 @@ management: exposure: include: 'health' health: + livenessstate: + enabled: true probes: enabled: true @@ -51,4 +53,4 @@ feign: config: default: connect-timeout: 500 - read-timeout: 500 \ No newline at end of file + read-timeout: 500 From 36c062b1f9bccbe69a45317cd5eebbb3d719f652 Mon Sep 17 00:00:00 2001 From: Sorin Stefan Iovita Date: Thu, 6 Aug 2020 15:20:42 +0300 Subject: [PATCH 03/28] Revert to test postgresql docker image using test-containers. (#694) --- common/persistence/pom.xml | 8 ++++---- .../db/specific/h2/V2__createPermissions.sql | 1 - .../src/test/resources/application.yaml | 6 ++---- services/distribution/pom.xml | 6 ++++++ .../src/test/resources/application.yaml | 14 ++++++-------- services/submission/pom.xml | 6 ++++++ .../submission/src/test/resources/application.yaml | 6 +----- 7 files changed, 25 insertions(+), 22 deletions(-) delete mode 100644 common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index f067ad341e..9dd03b053b 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -82,10 +82,10 @@ runtime - com.h2database - h2 - 1.4.200 - runtime + org.testcontainers + postgresql + 1.14.3 + test diff --git a/common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql b/common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql deleted file mode 100644 index 2f7af07d54..0000000000 --- a/common/persistence/src/main/resources/db/specific/h2/V2__createPermissions.sql +++ /dev/null @@ -1 +0,0 @@ --- no permissions on H2 necessary diff --git a/common/persistence/src/test/resources/application.yaml b/common/persistence/src/test/resources/application.yaml index 1dde9bb9cf..f949742728 100644 --- a/common/persistence/src/test/resources/application.yaml +++ b/common/persistence/src/test/resources/application.yaml @@ -2,11 +2,9 @@ spring: flyway: enabled: true - locations: classpath:/db/migration, classpath:/db/specific/{vendor} + locations: classpath:/db/migration datasource: - url: jdbc:h2:mem:test;MODE=PostgreSQL - driverClassName: org.h2.Driver - platform: h2 + url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw test: database: # Use datasource as defined above. diff --git a/services/distribution/pom.xml b/services/distribution/pom.xml index 1ab6d6df30..e37632cb75 100644 --- a/services/distribution/pom.xml +++ b/services/distribution/pom.xml @@ -99,6 +99,12 @@ org.springframework.boot spring-boot-starter-aop + + org.testcontainers + postgresql + 1.14.3 + test + diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index d779aec0f1..346dbf9b74 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -55,11 +55,9 @@ services: spring: main: banner-mode: off - datasource: - url: jdbc:h2:mem:test;MODE=PostgreSQL - driverClassName: org.h2.Driver - platform: h2 - test: - database: - # Use datasource as defined above. - replace: none + flyway: + enabled: true + locations: classpath:db/migration/postgres + jpa: + hibernate: + ddl-auto: validate diff --git a/services/submission/pom.xml b/services/submission/pom.xml index ac670c764b..437bbfa3ba 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -68,6 +68,12 @@ 2.26.3 test + + org.testcontainers + postgresql + 1.14.3 + test + diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 71d9af5252..3b51df00a8 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -8,14 +8,10 @@ spring: main: banner-mode: off datasource: - url: jdbc:h2:mem:test;MODE=PostgreSQL - driverClassName: org.h2.Driver - platform: h2 + url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw test: database: - # Use datasource as defined above. replace: none - services: submission: initial-fake-delay-milliseconds: 1 From db83e31c61485317aa8220c3a841e47b0a692452 Mon Sep 17 00:00:00 2001 From: EugenM-SAP <67458405+EugenM-SAP@users.noreply.github.com> Date: Wed, 12 Aug 2020 17:17:26 +0300 Subject: [PATCH 04/28] Test cases and debug support.for submission (#698) * Add integration test for future submission payload debugging * Added more test cases around invalid temp key parameter scenarios * Add support for printing the submission payload during tests * Enhance debug test with sql statements generation * Update services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java Co-authored-by: Michael Frey Co-authored-by: Hilmar Falkenberg Co-authored-by: Michael Frey --- services/submission/pom.xml | 6 + .../assertions/SubmissionAssertions.java | 67 +++++++++ .../controller/SubmissionControllerTest.java | 108 ++++++++------- .../integration/SubmissionPersistenceIT.java | 131 ++++++++++++++++++ .../src/test/resources/application.yaml | 6 + .../src/test/resources/db/clean_db_state.sql | 1 + .../payload/mobile-client-payload.pb | Bin 0 -> 390 bytes 7 files changed, 266 insertions(+), 53 deletions(-) create mode 100644 services/submission/src/test/java/app/coronawarn/server/services/submission/assertions/SubmissionAssertions.java create mode 100644 services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java create mode 100644 services/submission/src/test/resources/db/clean_db_state.sql create mode 100644 services/submission/src/test/resources/payload/mobile-client-payload.pb diff --git a/services/submission/pom.xml b/services/submission/pom.xml index 437bbfa3ba..c9011241b8 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -74,6 +74,12 @@ 1.14.3 test + + com.google.protobuf + protobuf-java-util + 3.12.4 + test + diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/assertions/SubmissionAssertions.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/assertions/SubmissionAssertions.java new file mode 100644 index 0000000000..725eff12f3 --- /dev/null +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/assertions/SubmissionAssertions.java @@ -0,0 +1,67 @@ +/*- + * ---license-start + * Corona-Warn-App + * --- + * Copyright (C) 2020 SAP SE 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 app.coronawarn.server.services.submission.assertions; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import app.coronawarn.server.common.persistence.domain.DiagnosisKey; +import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; +import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; + +public final class SubmissionAssertions { + + public static void assertElementsCorrespondToEachOther(Collection submittedTemporaryExposureKeys, + Collection savedDiagnosisKeys, SubmissionServiceConfig config) { + + Set submittedDiagnosisKeys = submittedTemporaryExposureKeys.stream() + .map(submittedDiagnosisKey -> DiagnosisKey.builder().fromProtoBuf(submittedDiagnosisKey).build()) + .collect(Collectors.toSet()); + + assertThat(savedDiagnosisKeys).hasSize(submittedDiagnosisKeys.size() * config.getRandomKeyPaddingMultiplier()); + assertThat(savedDiagnosisKeys).containsAll(submittedDiagnosisKeys); + + submittedDiagnosisKeys.forEach(submittedDiagnosisKey -> { + List savedKeysForSingleSubmittedKey = savedDiagnosisKeys.stream() + .filter(savedDiagnosisKey -> savedDiagnosisKey.getRollingPeriod() == submittedDiagnosisKey.getRollingPeriod()) + .filter(savedDiagnosisKey -> savedDiagnosisKey.getTransmissionRiskLevel() == submittedDiagnosisKey + .getTransmissionRiskLevel()) + .filter(savedDiagnosisKey -> savedDiagnosisKey.getRollingStartIntervalNumber() == submittedDiagnosisKey + .getRollingStartIntervalNumber()) + .collect(Collectors.toList()); + + assertThat(savedKeysForSingleSubmittedKey).hasSize(config.getRandomKeyPaddingMultiplier()); + assertThat(savedKeysForSingleSubmittedKey.stream() + .filter(savedKey -> Arrays.equals(savedKey.getKeyData(), submittedDiagnosisKey.getKeyData()))).hasSize(1); + assertThat(savedKeysForSingleSubmittedKey) + .allMatch(savedKey -> savedKey.getRollingPeriod() == submittedDiagnosisKey.getRollingPeriod()); + assertThat(savedKeysForSingleSubmittedKey).allMatch(savedKey -> savedKey + .getRollingStartIntervalNumber() == submittedDiagnosisKey.getRollingStartIntervalNumber()); + assertThat(savedKeysForSingleSubmittedKey).allMatch( + savedKey -> savedKey.getTransmissionRiskLevel() == submittedDiagnosisKey.getTransmissionRiskLevel()); + }); + } +} diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java index c9c5800b46..66fc94e627 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java @@ -26,6 +26,7 @@ import static app.coronawarn.server.services.submission.controller.RequestExecutor.buildPayloadWithOneKey; import static app.coronawarn.server.services.submission.controller.RequestExecutor.buildTemporaryExposureKey; import static app.coronawarn.server.services.submission.controller.RequestExecutor.createRollingStartIntervalNumber; +import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.any; @@ -40,6 +41,7 @@ import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.METHOD_NOT_ALLOWED; import static org.springframework.http.HttpStatus.OK; +import static app.coronawarn.server.services.submission.assertions.SubmissionAssertions.*; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.persistence.service.DiagnosisKeyService; @@ -49,15 +51,19 @@ import app.coronawarn.server.services.submission.monitoring.SubmissionMonitor; import app.coronawarn.server.services.submission.verification.TanVerifier; import com.google.protobuf.ByteString; + +import java.time.Instant; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -73,6 +79,7 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles({"disable-ssl-client-verification", "disable-ssl-client-verification-verify-hostname"}) +@TestInstance(Lifecycle.PER_CLASS) class SubmissionControllerTest { @MockBean @@ -111,9 +118,10 @@ void checkResponseStatusForValidParametersWithPadding() { assertThat(actResponse.getStatusCode()).isEqualTo(OK); } - @Test - void check400ResponseStatusForInvalidParameters() { - ResponseEntity actResponse = executor.executePost(buildPayloadWithInvalidKey()); + @ParameterizedTest + @MethodSource({"buildPayloadWithInvalidKeys"}) + void check400ResponseStatusForInvalidKeys(SubmissionPayload invalidPayload ) { + ResponseEntity actResponse = executor.executePost(invalidPayload); assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } @@ -138,7 +146,7 @@ void keysWithOutdatedRollingStartIntervalNumberDoNotGetSaved() { verify(diagnosisKeyService, atLeastOnce()).saveDiagnosisKeys(argument.capture()); submittedKeys.remove(outdatedKey); - assertElementsCorrespondToEachOther(submittedKeys, argument.getValue()); + assertElementsCorrespondToEachOther(submittedKeys, argument.getValue(), config); } @Test @@ -150,7 +158,7 @@ void checkSaveOperationCallAndFakeDelayUpdateForValidParameters() { verify(diagnosisKeyService, atLeastOnce()).saveDiagnosisKeys(argument.capture()); verify(fakeDelayManager, times(1)).updateFakeRequestDelay(anyLong()); - assertElementsCorrespondToEachOther(submittedKeys, argument.getValue()); + assertElementsCorrespondToEachOther(submittedKeys, argument.getValue(), config); } @ParameterizedTest @@ -162,14 +170,6 @@ void badRequestIfCwaHeadersMissing(HttpHeaders headers) { assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } - private static Stream createIncompleteHeaders() { - return Stream.of( - Arguments.of(HttpHeaderBuilder.builder().build()), - Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().build()), - Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().withoutCwaFake().build()), - Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().cwaAuth().build())); - } - @ParameterizedTest @MethodSource("createDeniedHttpMethods") void checkOnlyPostAllowed(HttpMethod deniedHttpMethod) { @@ -185,13 +185,6 @@ void checkOnlyPostAllowed(HttpMethod deniedHttpMethod) { .contains(actStatus); } - private static Stream createDeniedHttpMethods() { - return Arrays.stream(HttpMethod.values()) - .filter(method -> method != HttpMethod.POST) - .filter(method -> method != HttpMethod.PATCH) /* not supported by Rest Template */ - .map(Arguments::of); - } - @Test void invalidTanHandling() { when(tanVerifier.verifyTan(anyString())).thenReturn(false); @@ -278,41 +271,50 @@ private TemporaryExposureKey createOutdatedKey() { .setTransmissionRiskLevel(5).build(); } - private SubmissionPayload buildPayloadWithInvalidKey() { + private Stream buildPayloadWithInvalidKeys() { + return Stream.of( + Arguments.of(buildPayloadWithInvalidTransmissionRiskLevel()), + Arguments.of(buildPayloadWithNegativeRollingStartInterval()), + Arguments.of(buildPayloadWithFutureRollingStartInterval()), + Arguments.of(buildPayloadWithInvalidKeyData()) + ); + } + + private SubmissionPayload buildPayloadWithInvalidTransmissionRiskLevel() { TemporaryExposureKey invalidKey = buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2), 999); return buildPayload(invalidKey); } - private void assertElementsCorrespondToEachOther(Collection submittedTemporaryExposureKeys, - Collection savedDiagnosisKeys) { - - Set submittedDiagnosisKeys = submittedTemporaryExposureKeys.stream() - .map(submittedDiagnosisKey -> DiagnosisKey.builder().fromProtoBuf(submittedDiagnosisKey).build()) - .collect(Collectors.toSet()); - - assertThat(savedDiagnosisKeys).hasSize(submittedDiagnosisKeys.size() * config.getRandomKeyPaddingMultiplier()); - assertThat(savedDiagnosisKeys).containsAll(submittedDiagnosisKeys); - - submittedDiagnosisKeys.forEach(submittedDiagnosisKey -> { - List savedKeysForSingleSubmittedKey = savedDiagnosisKeys.stream() - .filter(savedDiagnosisKey -> savedDiagnosisKey.getRollingPeriod() == - submittedDiagnosisKey.getRollingPeriod()) - .filter(savedDiagnosisKey -> savedDiagnosisKey.getTransmissionRiskLevel() == - submittedDiagnosisKey.getTransmissionRiskLevel()) - .filter(savedDiagnosisKey -> savedDiagnosisKey.getRollingStartIntervalNumber() == - submittedDiagnosisKey.getRollingStartIntervalNumber()) - .collect(Collectors.toList()); - - assertThat(savedKeysForSingleSubmittedKey).hasSize(config.getRandomKeyPaddingMultiplier()); - assertThat(savedKeysForSingleSubmittedKey.stream().filter(savedKey -> - Arrays.equals(savedKey.getKeyData(), submittedDiagnosisKey.getKeyData()))).hasSize(1); - assertThat(savedKeysForSingleSubmittedKey).allMatch( - savedKey -> savedKey.getRollingPeriod() == submittedDiagnosisKey.getRollingPeriod()); - assertThat(savedKeysForSingleSubmittedKey).allMatch( - savedKey -> savedKey.getRollingStartIntervalNumber() == submittedDiagnosisKey - .getRollingStartIntervalNumber()); - assertThat(savedKeysForSingleSubmittedKey).allMatch( - savedKey -> savedKey.getTransmissionRiskLevel() == submittedDiagnosisKey.getTransmissionRiskLevel()); - }); + private SubmissionPayload buildPayloadWithNegativeRollingStartInterval() { + TemporaryExposureKey invalidKey = buildTemporaryExposureKey(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(-1), 5); + return buildPayload(invalidKey); + } + + private SubmissionPayload buildPayloadWithFutureRollingStartInterval() { + int currentPeriod = Math.toIntExact(LocalDateTime.ofInstant(Instant.now(), UTC).toEpochSecond(UTC) / 600L); + TemporaryExposureKey invalidKey = buildTemporaryExposureKey(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(currentPeriod), 5); + return buildPayload(invalidKey); + } + + private SubmissionPayload buildPayloadWithInvalidKeyData() { + TemporaryExposureKey invalidKey = buildTemporaryExposureKey(" ", createRollingStartIntervalNumber(2), 999); + return buildPayload(invalidKey); + } + + private static Stream createDeniedHttpMethods() { + return Arrays.stream(HttpMethod.values()) + .filter(method -> method != HttpMethod.POST) + .filter(method -> method != HttpMethod.PATCH) /* not supported by Rest Template */ + .map(Arguments::of); + } + + private static Stream createIncompleteHeaders() { + return Stream.of( + Arguments.of(HttpHeaderBuilder.builder().build()), + Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().build()), + Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().withoutCwaFake().build()), + Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().cwaAuth().build())); } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java new file mode 100644 index 0000000000..65527f87fd --- /dev/null +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java @@ -0,0 +1,131 @@ +/*- + * ---license-start + * Corona-Warn-App + * --- + * Copyright (C) 2020 SAP SE 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 app.coronawarn.server.services.submission.integration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static app.coronawarn.server.services.submission.assertions.SubmissionAssertions.*; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.tomcat.util.buf.StringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import com.google.common.io.BaseEncoding; +import com.google.protobuf.util.JsonFormat; +import app.coronawarn.server.common.persistence.service.DiagnosisKeyService; +import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; +import app.coronawarn.server.common.protocols.internal.SubmissionPayload; +import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; +import app.coronawarn.server.services.submission.controller.FakeDelayManager; +import app.coronawarn.server.services.submission.controller.RequestExecutor; +import app.coronawarn.server.services.submission.verification.TanVerifier; + +/** + * This test serves more like a dev tool which helps with debugging production issues. + * It inserts keys parsed from a proto buf file whos content was captured by the mobile + * client during requests to the server. The content of the current test resource file + * can be quickly replaced during the investigation of an issue. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles({ "disable-ssl-client-verification", "disable-ssl-client-verification-verify-hostname" }) +@Sql(scripts = {"classpath:db/clean_db_state.sql"}, + executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +public class SubmissionPersistenceIT { + + private static final Logger logger = LoggerFactory.getLogger(SubmissionPersistenceIT.class); + + @Autowired + private DiagnosisKeyService diagnosisKeyService; + + @Autowired + private RequestExecutor executor; + + @Autowired + private SubmissionServiceConfig config; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @MockBean + private TanVerifier tanVerifier; + + @MockBean + private FakeDelayManager fakeDelayManager; + + @BeforeEach + public void setUpMocks() { + when(tanVerifier.verifyTan(anyString())).thenReturn(true); + when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); + } + + @Disabled("Because the content of the .pb file becomes old and retention time passes, this test will fail. " + + "Enable when debugging of a new payload is required.") + @ParameterizedTest + @ValueSource(strings = { "src/test/resources/payload/mobile-client-payload.pb" }) + public void testKeyInsertionWithMobileClientProtoBuf(String testFile) throws IOException { + Path path = Paths.get(testFile); + InputStream input = new FileInputStream(path.toFile()); + SubmissionPayload payload = SubmissionPayload.parseFrom(input); + + logger.info("Submitting payload: " + System.lineSeparator() + + JsonFormat.printer().preservingProtoFieldNames().omittingInsignificantWhitespace().print(payload)); + + executor.executePost(payload); + + String presenceVerificationSql = generateDebugSqlStatement(payload); + logger.info("SQL debugging statement: " + System.lineSeparator() + presenceVerificationSql); + Integer result = jdbcTemplate.queryForObject(presenceVerificationSql, Integer.class); + + assertEquals(payload.getKeysList().size(), result); + assertElementsCorrespondToEachOther(payload.getKeysList(), diagnosisKeyService.getDiagnosisKeys(), config); + } + + private String generateDebugSqlStatement(SubmissionPayload payload) { + List base64Keys = payload.getKeysList() + .stream() + .map(key -> "'" + toBase64(key) + "'") + .collect(Collectors.toList()); + return "SELECT count(*) FROM diagnosis_key where ENCODE(key_data, 'BASE64') IN (" + + StringUtils.join(base64Keys, ',') + ")"; + } + + private String toBase64(TemporaryExposureKey key) { + return BaseEncoding.base64().encode((key.getKeyData()).toByteArray()); + } +} diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 3b51df00a8..908e1c9208 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -4,6 +4,12 @@ logging: org: springframework: off root: off + app: + coronawarn: + server: + services: + submission: + integration: info spring: main: banner-mode: off diff --git a/services/submission/src/test/resources/db/clean_db_state.sql b/services/submission/src/test/resources/db/clean_db_state.sql new file mode 100644 index 0000000000..95791a1f22 --- /dev/null +++ b/services/submission/src/test/resources/db/clean_db_state.sql @@ -0,0 +1 @@ +truncate table diagnosis_key; \ No newline at end of file diff --git a/services/submission/src/test/resources/payload/mobile-client-payload.pb b/services/submission/src/test/resources/payload/mobile-client-payload.pb new file mode 100644 index 0000000000000000000000000000000000000000..e1fe3efd374be5ff5de62e9df6970c700e48cf1a GIT binary patch literal 390 zcmd;*;SxAs;qZhZoYQ1Sk?Vs0e(`e!*d!ioUc{&{fsqR+tGUhWp0r}xhXuzBFKO)V z*dxFpabXi&_Qbmx3)i0LxMp)CfoA64FPBfh>1RI>x1{uL{n4)%1(+o!tb@z$ z@QwWRv-MYonh;+?qQ=3A0*n$3YvHn6>gAu^){l6$>f!2$lz+=t0cAg|hRf=F*r!<+6|=8RtyO<%9N)HBL{%jWD7#@fT=vaNmKVEPyKL4Ae7eNsrL+Vn JyI>hu767dtmmvTE literal 0 HcmV?d00001 From 12b310ad45a7401f8723d3fb1bab442fe95cd180 Mon Sep 17 00:00:00 2001 From: Evgenii Skrebtcov <67064767+EvgeniiSkrebtcov@users.noreply.github.com> Date: Wed, 12 Aug 2020 16:30:44 +0200 Subject: [PATCH 05/28] Update maven.config with current version 1.4.0-SNAPSHOT (#697) Set current version to 1.3.0-SNAPSHOT Co-authored-by: Hilmar Falkenberg --- .mvn/maven.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index 80dd529775..6147f32101 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,4 +1,4 @@ --Drevision=1.3.0-SNAPSHOT +-Drevision=1.4.0-SNAPSHOT -Dlicense.projectName=Corona-Warn-App -Dlicense.inceptionYear=2020 -Dlicense.licenseName=apache_v2 From 9d93f17dd86af00ac96bdf6be2cd189bd8fab1ea Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Thu, 13 Aug 2020 11:51:06 +0300 Subject: [PATCH 06/28] -fix invalid test and name refactoring (#703) --- .../controller/PayloadValidationTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java index 7470f8fae6..cbd427182a 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java @@ -76,7 +76,7 @@ void check400ResponseStatusForTooManyKeys() { private Collection buildPayloadWithTooManyKeys() { ArrayList tooMany = new ArrayList<>(); for (int i = 0; i <= 20; i++) { - tooMany.add(buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2), 3)); + tooMany.add(buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2) + i * DiagnosisKey.EXPECTED_ROLLING_PERIOD , 3)); } return tooMany; } @@ -98,12 +98,12 @@ void check200ResponseStatusForGapsInTimeIntervals() { int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + 2 * DiagnosisKey.EXPECTED_ROLLING_PERIOD; - var keysWithDuplicateStartIntervalNumber = Lists.list( + var keysWithGapsInStartIntervalNumber = Lists.list( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3), buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); - ResponseEntity actResponse = executor.executePost(keysWithDuplicateStartIntervalNumber); + ResponseEntity actResponse = executor.executePost(keysWithGapsInStartIntervalNumber); assertThat(actResponse.getStatusCode()).isEqualTo(OK); } @@ -112,11 +112,11 @@ void check200ResponseStatusForGapsInTimeIntervals() { void check400ResponseStatusForOverlappingTimeIntervals() { int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + (DiagnosisKey.EXPECTED_ROLLING_PERIOD / 2); - var keysWithDuplicateStartIntervalNumber = Lists.list( + var keysWithOverlappingStartIntervalNumber = Lists.list( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); - ResponseEntity actResponse = executor.executePost(keysWithDuplicateStartIntervalNumber); + ResponseEntity actResponse = executor.executePost(keysWithOverlappingStartIntervalNumber); assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } @@ -126,12 +126,12 @@ void check200ResponseStatusForValidSubmissionPayload() { int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; - var keysWithDuplicateStartIntervalNumber = Lists.list( + var keysWithValidStartIntervalNumber = Lists.list( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3), buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); - ResponseEntity actResponse = executor.executePost(keysWithDuplicateStartIntervalNumber); + ResponseEntity actResponse = executor.executePost(keysWithValidStartIntervalNumber); assertThat(actResponse.getStatusCode()).isEqualTo(OK); } From 4b188b9dbb0574ed74efc1444919cf6bea269604 Mon Sep 17 00:00:00 2001 From: Sorin Stefan Iovita Date: Thu, 13 Aug 2020 14:24:09 +0300 Subject: [PATCH 07/28] Add app-features as external configuration (#704) * Add app features in configuration file Adjust tests * Rename env variable for plausible deniability active. Remove env name for app feature label. * Adjust test application.yaml * Change active type from boolean to integer * Change app feature 'active' property to 'value' --- .../protocols/internal/app_config.proto | 3 ++ .../protocols/internal/app_features.proto | 13 ++++++ ...icationConfigurationPublicationConfig.java | 11 ++++- .../config/DistributionServiceConfig.java | 45 +++++++++++++++++++ .../src/main/resources/application.yaml | 4 ++ ...pplicationConfigurationMasterFileTest.java | 7 ++- .../src/test/resources/application.yaml | 4 ++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_features.proto diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto index 2aa33b9a00..f21b7806cd 100644 --- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto @@ -6,6 +6,7 @@ import "app/coronawarn/server/common/protocols/internal/risk_score_classificatio import "app/coronawarn/server/common/protocols/internal/risk_score_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/app_version_config.proto"; import "app/coronawarn/server/common/protocols/internal/attenuation_duration.proto"; +import "app/coronawarn/server/common/protocols/internal/app_features.proto"; message ApplicationConfiguration { @@ -18,4 +19,6 @@ message ApplicationConfiguration { app.coronawarn.server.common.protocols.internal.AttenuationDuration attenuationDuration = 4; app.coronawarn.server.common.protocols.internal.ApplicationVersionConfiguration appVersion = 5; + + app.coronawarn.server.common.protocols.internal.AppFeatures appFeatures = 6; } diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_features.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_features.proto new file mode 100644 index 0000000000..9e1205609e --- /dev/null +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_features.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package app.coronawarn.server.common.protocols.internal; +option java_package = "app.coronawarn.server.common.protocols.internal"; +option java_multiple_files = true; + +message AppFeatures { + repeated AppFeature app_features = 1; +} + +message AppFeature { + string label = 1; + int32 value = 2; +} diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java index ba5bddc29e..c06facaa7e 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java @@ -20,7 +20,10 @@ package app.coronawarn.server.services.distribution.assembly.appconfig; +import app.coronawarn.server.common.protocols.internal.AppFeatures; import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration; +import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -49,7 +52,11 @@ public class ApplicationConfigurationPublicationConfig { * @throws UnableToLoadFileException when the file/transformation did not succeed */ @Bean - public ApplicationConfiguration createMasterConfiguration() throws UnableToLoadFileException { - return YamlLoader.loadYamlIntoProtobufBuilder(MASTER_FILE, ApplicationConfiguration.Builder.class).build(); + public ApplicationConfiguration createMasterConfiguration(DistributionServiceConfig distributionServiceConfig) + throws UnableToLoadFileException { + return YamlLoader.loadYamlIntoProtobufBuilder(MASTER_FILE, Builder.class) + .setAppFeatures( + AppFeatures.newBuilder().addAllAppFeatures(distributionServiceConfig.getAppFeaturesProto()).build()) + .build(); } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java index ea77dbcd4f..e6e00582e5 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java @@ -21,6 +21,8 @@ package app.coronawarn.server.services.distribution.config; import app.coronawarn.server.common.protocols.external.exposurenotification.SignatureInfo; +import java.util.List; +import java.util.stream.Collectors; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.Pattern; @@ -68,6 +70,7 @@ public class DistributionServiceConfig { private Signature signature; private Api api; private ObjectStore objectStore; + private List appFeatures; public Paths getPaths() { return paths; @@ -174,6 +177,26 @@ public void setObjectStore( this.objectStore = objectStore; } + public List getAppFeatures() { + return appFeatures; + } + + public void setAppFeatures(List appFeatures) { + this.appFeatures = appFeatures; + } + + /** + * Get app features as list of protobuf objects. + * @return list of {@link app.coronawarn.server.common.protocols.internal.AppFeature} + */ + public List getAppFeaturesProto() { + return getAppFeatures().stream() + .map(appFeature -> app.coronawarn.server.common.protocols.internal.AppFeature.newBuilder() + .setLabel(appFeature.getLabel()) + .setValue(appFeature.getValue()).build()) + .collect(Collectors.toList()); + } + public static class TekExport { @Pattern(regexp = FILE_NAME_WITH_TYPE_REGEX) @@ -538,4 +561,26 @@ public void setForceUpdateKeyfiles(Boolean forceUpdateKeyfiles) { this.forceUpdateKeyfiles = forceUpdateKeyfiles; } } + + private static class AppFeature { + + private String label; + private Integer value; + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } + } } diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index d635e9e7f9..bf0dae2185 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -82,6 +82,10 @@ services: # Allows distribution to overwrite files which are published on the object store force-update-keyfiles: ${FORCE_UPDATE_KEYFILES:false} + app-features: + - label: isPlausibleDeniabilityActive + value: ${PLAUSIBLE_DENIABILITY_ACTIVE:1} + spring: main: web-application-type: NONE diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationMasterFileTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationMasterFileTest.java index 07a297dd80..47c6eedde8 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationMasterFileTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationMasterFileTest.java @@ -25,14 +25,19 @@ import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration; import app.coronawarn.server.services.distribution.assembly.appconfig.validation.ApplicationConfigurationValidator; import app.coronawarn.server.services.distribution.assembly.appconfig.validation.ValidationResult; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; +@EnableConfigurationProperties(value = DistributionServiceConfig.class) @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApplicationConfigurationPublicationConfig.class) +@ContextConfiguration(classes = ApplicationConfigurationPublicationConfig.class, + initializers = ConfigFileApplicationContextInitializer.class) class ApplicationConfigurationMasterFileTest { private static final ValidationResult SUCCESS = new ValidationResult(); diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 346dbf9b74..62cd028177 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -52,6 +52,10 @@ services: max-number-of-failed-operations: 5 max-number-of-s3-threads: 2 force-update-keyfiles: ${FORCE_UPDATE_KEYFILES:false} + + app-features: + - label: isPlausibleDeniabilityActive + value: ${PLAUSIBLE_DENIABILITY_ACTIVE:1} spring: main: banner-mode: off From 47f61e0691dc838b5f8e125961ec7b565ed4f8cd Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 14 Aug 2020 08:31:15 +0200 Subject: [PATCH 08/28] use same version for spring-boot-starter-parent accross all components (#709) --- common/persistence/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index 9dd03b053b..d378847fc5 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.1.RELEASE + 2.3.2.RELEASE 4.0.0 From 8c2fea20c9e234db0ec5bda038296ecda7adee2f Mon Sep 17 00:00:00 2001 From: Frederico Date: Tue, 18 Aug 2020 10:28:07 +0200 Subject: [PATCH 09/28] Fix/700 retention policy (#711) * fix retention policy of stored diagnosis keys * fix typo * fix failing test case due to previously injected time * refactor method names --- .../persistence/repository/DiagnosisKeyRepository.java | 8 ++++---- .../common/persistence/service/DiagnosisKeyService.java | 4 ++-- .../persistence/service/DiagnosisKeyServiceTest.java | 4 ++-- .../services/distribution/runner/TestDataGeneration.java | 4 ++-- .../integration/ObjectStoreFilePreservationIT.java | 2 +- .../distribution/runner/TestDataGenerationTest.java | 7 +++++++ 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepository.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepository.java index cfec2bc6ef..22bdf9dedb 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepository.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepository.java @@ -36,8 +36,8 @@ public interface DiagnosisKeyRepository extends PagingAndSortingRepository diagnosisKeys) { return getRetentionStartTimestamp(); } else { DiagnosisKey latestDiagnosisKey = diagnosisKeys.get(diagnosisKeys.size() - 1); - return latestDiagnosisKey.getSubmissionTimestamp(); + return latestDiagnosisKey.getSubmissionTimestamp() + 1; } } diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/integration/ObjectStoreFilePreservationIT.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/integration/ObjectStoreFilePreservationIT.java index e119a66e46..28a0cce2c5 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/integration/ObjectStoreFilePreservationIT.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/objectstore/integration/ObjectStoreFilePreservationIT.java @@ -132,7 +132,7 @@ void files_once_published_to_objectstore_should_not_be_overriden_because_of_rete triggerRetentionPolicy(testStartDate); - // Trigger second distrubution after data retention policies were applied + // Trigger second distribution after data retention policies were applied assembleAndDistribute(testOutputFolder.newFolder("output-after-retention")); List filesAfterRetention = getPublishedFiles(); diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java index c8eb644d31..422c636071 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java @@ -25,7 +25,9 @@ import app.coronawarn.server.services.distribution.assembly.structure.util.TimeUtils; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.TestData; +import org.junit.After; import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -76,6 +78,11 @@ void setup() { distributionServiceConfig.setTestData(testData); } + @AfterEach + void tearDown() { + TimeUtils.setNow(null); + } + @Test void shouldCreateKeysAllKeys() { var now = LocalDateTime.of(2020, 7, 15, 12, 0, 0).toInstant(ZoneOffset.UTC); From 87eb979aa5510ea121dcae8bb39564821e208a99 Mon Sep 17 00:00:00 2001 From: emmetsap Date: Mon, 24 Aug 2020 06:17:49 +0100 Subject: [PATCH 10/28] Submission Service - updated documentation (#713) * Updated the validation for the submission service in SUMBISSION.md to match the new rules for TEK's * markdownlint fixes * Update docs/SUBMISSION.md Co-authored-by: Sorin Stefan Iovita Co-authored-by: Ioan Gut Co-authored-by: ioangut <67064882+ioangut@users.noreply.github.com> Co-authored-by: Sorin Stefan Iovita --- docs/SUBMISSION.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/SUBMISSION.md b/docs/SUBMISSION.md index 92be86cf51..84595e623b 100644 --- a/docs/SUBMISSION.md +++ b/docs/SUBMISSION.md @@ -37,6 +37,13 @@ You will find the implementation file at [`/services/submission/src/main/java/ap ### Validation Constraints -* `StartIntervalNumber` values from the same [`SubmissionPayload`](https://corona-warn-app.github.io/cwa-server/1.0.0/app/coronawarn/server/common/protocols/internal/SubmissionPayload.html) shall be unique. -* There must not be any keys in the [`SubmissionPayload`](https://corona-warn-app.github.io/cwa-server/1.0.0/app/coronawarn/server/common/protocols/internal/SubmissionPayload.html) that have overlapping time windows. -* The period covered by the data file must not exceed the configured maximum number of days, which is defined by `max-number-of-keys` property in [`application.yaml`](/services/submission/src/main/resources/application.yaml). Currently no submissions with more than 14 keys are accepted +Temporary Exposure Keys (TEK's) are submitted by the client device (iOS/Android phone) via the Submission Service. + +Constraints maintained as enviroment variables which are present as secrets in the Vault /cwa-server/submission + +The constraints put on submitted TEK's are as follows: + +* Each TEK contains a `StartIntervalNumber` (a date e.g. 2nd July 2020) +* The period covered by the data file must not exceed the configured maximum number of days, represented by the `MAX_NUMBER_OF_KEYS` property which is in the vault. +* The total combined rolling period for a single TEK cannot exceed maximum rolling period, represented by the `MAX_ROLLING_PERIOD` property which is in the vault. +* More than one TEK with the same `StartIntervalNumber` may be submitted, these will have their rolling period's combined. From db18b68cb5c7bc62f9f281a16423290b553ca726 Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Tue, 25 Aug 2020 10:06:26 +0300 Subject: [PATCH 11/28] Submission Service - Enhance TEK's (#712) * -initial draft commit * updated checkDuplicateStartIntervalNumberLimit wip * updated checkDuplicateStartIntervalNumberLimit * -fix test broke after solved conflict * changed checkDuplicateStartIntervalNumberLimit to use HashMap * -fix checklist reports * add env variables for submission service * review update add more tests * solved code smells * rename attribute maxRollingPeriod * renamed getter getMaxRollingPeriod * Update ValidSubmissionPayload.java * refactor implementation for consistency * fix checkstyle errors modify the Violation text accordingly * fix code smell * Update ValidSubmissionPayload.java * Change validation for payloads based on rolling period * Capture more test scenarios * add checkstyle indentation * create test data generation class * add header to the new class * resolved review conversation * fixed checkstyle errors * resolved code smell * fix compile error * create valid TEK, fix test failing Co-authored-by: Emmet Co-authored-by: Eugen M Co-authored-by: Pit Humke --- .../persistence/domain/DiagnosisKey.java | 7 +- .../domain/DiagnosisKeyBuilder.java | 2 +- .../domain/DiagnosisKeyBuilders.java | 2 +- .../domain/DiagnosisKeyBuilderTest.java | 21 +-- ...agnosisKeyServiceMockedRepositoryTest.java | 4 +- .../config/SubmissionServiceConfig.java | 14 +- .../validation/ValidSubmissionPayload.java | 56 +++++--- .../src/main/resources/application.yaml | 6 +- .../controller/PayloadValidationTest.java | 135 ++++++++++++++---- .../controller/RequestExecutor.java | 11 +- .../controller/SubmissionControllerTest.java | 6 +- .../controller/TEKDatasetGeneration.java | 113 +++++++++++++++ .../src/test/resources/application.yaml | 1 + 13 files changed, 308 insertions(+), 70 deletions(-) create mode 100644 services/submission/src/test/java/app/coronawarn/server/services/submission/controller/TEKDatasetGeneration.java diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKey.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKey.java index b38813076d..be011417ae 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKey.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKey.java @@ -47,7 +47,8 @@ public class DiagnosisKey { * reject any diagnosis keys that do not have a rolling period of a certain fixed value. See * https://developer.apple.com/documentation/exposurenotification/setting_up_an_exposure_notification_server */ - public static final int EXPECTED_ROLLING_PERIOD = 144; + public static final int MIN_ROLLING_PERIOD = 0; + public static final int MAX_ROLLING_PERIOD = 144; private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); @@ -58,8 +59,8 @@ public class DiagnosisKey { @ValidRollingStartIntervalNumber private final int rollingStartIntervalNumber; - @Range(min = EXPECTED_ROLLING_PERIOD, max = EXPECTED_ROLLING_PERIOD, - message = "Rolling period must be " + EXPECTED_ROLLING_PERIOD + ".") + @Range(min = MIN_ROLLING_PERIOD, max = MAX_ROLLING_PERIOD, + message = "Rolling period must be between " + MIN_ROLLING_PERIOD + " and " + MAX_ROLLING_PERIOD + ".") private final int rollingPeriod; @Range(min = 0, max = 8, message = "Risk level must be between 0 and 8.") diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilder.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilder.java index f9acc13c88..93e473d9e6 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilder.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilder.java @@ -46,7 +46,7 @@ public class DiagnosisKeyBuilder implements private byte[] keyData; private int rollingStartIntervalNumber; - private int rollingPeriod = DiagnosisKey.EXPECTED_ROLLING_PERIOD; + private int rollingPeriod = DiagnosisKey.MAX_ROLLING_PERIOD; private int transmissionRiskLevel; private Long submissionTimestamp = null; diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilders.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilders.java index fd1479d82f..46a5b2ac19 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilders.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilders.java @@ -81,7 +81,7 @@ interface FinalBuilder { /** * Adds the specified rolling period to this builder. If not specified, the rolling period defaults to {@link - * DiagnosisKey#EXPECTED_ROLLING_PERIOD} + * DiagnosisKey#MAX_ROLLING_PERIOD} * * @param rollingPeriod Number describing how long a key is valid. It is expressed in increments of 10 minutes (e.g. * 144 for 24 hours). diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilderTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilderTest.java index 22c128137e..d577ba6a69 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilderTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyBuilderTest.java @@ -51,7 +51,7 @@ void buildFromProtoBufObjWithSubmissionTimestamp() { .newBuilder() .setKeyData(ByteString.copyFrom(this.expKeyData)) .setRollingStartIntervalNumber(this.expRollingStartIntervalNumber) - .setRollingPeriod(DiagnosisKey.EXPECTED_ROLLING_PERIOD) + .setRollingPeriod(DiagnosisKey.MAX_ROLLING_PERIOD) .setTransmissionRiskLevel(this.expTransmissionRiskLevel) .build(); @@ -69,7 +69,7 @@ void buildFromProtoBufObjWithoutSubmissionTimestamp() { .newBuilder() .setKeyData(ByteString.copyFrom(this.expKeyData)) .setRollingStartIntervalNumber(this.expRollingStartIntervalNumber) - .setRollingPeriod(DiagnosisKey.EXPECTED_ROLLING_PERIOD) + .setRollingPeriod(DiagnosisKey.MAX_ROLLING_PERIOD) .setTransmissionRiskLevel(this.expTransmissionRiskLevel) .build(); @@ -106,7 +106,7 @@ void buildSuccessivelyWithRollingPeriod() { .withRollingStartIntervalNumber(this.expRollingStartIntervalNumber) .withTransmissionRiskLevel(this.expTransmissionRiskLevel) .withSubmissionTimestamp(this.expSubmissionTimestamp) - .withRollingPeriod(DiagnosisKey.EXPECTED_ROLLING_PERIOD).build(); + .withRollingPeriod(DiagnosisKey.MAX_ROLLING_PERIOD).build(); assertDiagnosisKeyEquals(actDiagnosisKey, this.expSubmissionTimestamp); } @@ -165,17 +165,18 @@ void transmissionRiskLevelDoesNotThrowForValid(int validRiskLevel) { } @ParameterizedTest - @ValueSource(ints = {-3, 143, 145}) + @ValueSource(ints = {-3, 145}) void rollingPeriodMustBeEpectedValue(int invalidRollingPeriod) { assertThat(catchThrowable(() -> keyWithRollingPeriod(invalidRollingPeriod))) .isInstanceOf(InvalidDiagnosisKeyException.class) - .hasMessage("[Rolling period must be " + DiagnosisKey.EXPECTED_ROLLING_PERIOD + .hasMessage("[Rolling period must be between "+ DiagnosisKey.MIN_ROLLING_PERIOD + " and " + DiagnosisKey.MAX_ROLLING_PERIOD + ". Invalid Value: " + invalidRollingPeriod + "]"); } - @Test - void rollingPeriodDoesNotThrowForValid() { - assertThatCode(() -> keyWithRollingPeriod(DiagnosisKey.EXPECTED_ROLLING_PERIOD)).doesNotThrowAnyException(); + @ParameterizedTest + @ValueSource(ints = {DiagnosisKey.MIN_ROLLING_PERIOD, 100, DiagnosisKey.MAX_ROLLING_PERIOD}) + void rollingPeriodDoesNotThrowForValid(int validRollingPeriod) { + assertThatCode(() -> keyWithRollingPeriod(validRollingPeriod)).doesNotThrowAnyException(); } @ParameterizedTest @@ -218,7 +219,7 @@ void submissionTimestampDoesNotThrowOnValid() { () -> buildDiagnosisKeyForSubmissionTimestamp(Instant.now().minus(Duration.ofHours(2)).getEpochSecond() / SECONDS_PER_HOUR)) .doesNotThrowAnyException(); } - + private DiagnosisKey keyWithKeyData(byte[] expKeyData) { return DiagnosisKey.builder() .withKeyData(expKeyData) @@ -260,7 +261,7 @@ private void assertDiagnosisKeyEquals(DiagnosisKey actDiagnosisKey, long expSubm assertThat(actDiagnosisKey.getSubmissionTimestamp()).isEqualTo(expSubmissionTimestamp); assertThat(actDiagnosisKey.getKeyData()).isEqualTo(this.expKeyData); assertThat(actDiagnosisKey.getRollingStartIntervalNumber()).isEqualTo(this.expRollingStartIntervalNumber); - assertThat(actDiagnosisKey.getRollingPeriod()).isEqualTo(DiagnosisKey.EXPECTED_ROLLING_PERIOD); + assertThat(actDiagnosisKey.getRollingPeriod()).isEqualTo(DiagnosisKey.MAX_ROLLING_PERIOD); assertThat(actDiagnosisKey.getTransmissionRiskLevel()).isEqualTo(this.expTransmissionRiskLevel); } } diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyServiceMockedRepositoryTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyServiceMockedRepositoryTest.java index 281461cdeb..57f8a0b037 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyServiceMockedRepositoryTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/DiagnosisKeyServiceMockedRepositoryTest.java @@ -85,12 +85,12 @@ private void mockInvalidKeyInDb(List keys) { private DiagnosisKey validKey(long expSubmissionTimestamp) { return new DiagnosisKey(expKeyData, expRollingStartIntervalNumber, - DiagnosisKey.EXPECTED_ROLLING_PERIOD, expTransmissionRiskLevel, expSubmissionTimestamp); + DiagnosisKey.MAX_ROLLING_PERIOD, expTransmissionRiskLevel, expSubmissionTimestamp); } private DiagnosisKey invalidKey(long expSubmissionTimestamp) { byte[] expKeyData = "17--bytelongarray".getBytes(StandardCharsets.US_ASCII); return new DiagnosisKey(expKeyData, expRollingStartIntervalNumber, - DiagnosisKey.EXPECTED_ROLLING_PERIOD, expTransmissionRiskLevel, expSubmissionTimestamp); + DiagnosisKey.MAX_ROLLING_PERIOD, expTransmissionRiskLevel, expSubmissionTimestamp); } } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java index 7babed0f48..1ba2bfdac1 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/config/SubmissionServiceConfig.java @@ -59,6 +59,10 @@ public class SubmissionServiceConfig { private Verification verification; private Monitoring monitoring; private Client client; + @Min(0) + @Max(144) + private Integer maxRollingPeriod; + public Long getInitialFakeDelayMilliseconds() { return initialFakeDelayMilliseconds; @@ -108,6 +112,14 @@ public void setMaximumRequestSize(DataSize maximumRequestSize) { this.maximumRequestSize = maximumRequestSize; } + public Integer getMaxRollingPeriod() { + return maxRollingPeriod; + } + + public void setMaxRollingPeriod(Integer maxRollingPeriod) { + this.maxRollingPeriod = maxRollingPeriod; + } + public Integer getMaxNumberOfKeys() { return payload.getMaxNumberOfKeys(); } @@ -119,7 +131,7 @@ public void setPayload(Payload payload) { private static class Payload { @Min(7) - @Max(28) + @Max(100) private Integer maxNumberOfKeys; public Integer getMaxNumberOfKeys() { diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java index b5a7547438..b5d0cfe172 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java @@ -20,8 +20,6 @@ package app.coronawarn.server.services.submission.validation; -import static app.coronawarn.server.common.persistence.domain.DiagnosisKey.EXPECTED_ROLLING_PERIOD; - import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; @@ -32,6 +30,7 @@ import java.lang.annotation.Target; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @@ -68,9 +67,11 @@ class SubmissionPayloadValidator implements ConstraintValidator { private final int maxNumberOfKeys; + private final int maxRollingPeriod; public SubmissionPayloadValidator(SubmissionServiceConfig submissionServiceConfig) { maxNumberOfKeys = submissionServiceConfig.getMaxNumberOfKeys(); + maxRollingPeriod = submissionServiceConfig.getMaxRollingPeriod(); } /** @@ -87,11 +88,14 @@ public boolean isValid(SubmissionPayload submissionPayload, ConstraintValidatorC List exposureKeys = submissionPayload.getKeysList(); validatorContext.disableDefaultConstraintViolation(); - boolean isValid = checkKeyCollectionSize(exposureKeys, validatorContext); - isValid &= checkUniqueStartIntervalNumbers(exposureKeys, validatorContext); - isValid &= checkNoOverlapsInTimeWindow(exposureKeys, validatorContext); - - return isValid; + if (keysHaveFlexibleRollingPeriod(exposureKeys)) { + return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext) + && checkKeysCumulateEqualOrLessThanMaxRollingPeriodPerDay(exposureKeys, validatorContext); + } else { + return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext) + && checkKeyCollectionSize(exposureKeys, validatorContext) + && checkUniqueStartIntervalNumbers(exposureKeys, validatorContext); + } } private void addViolation(ConstraintValidatorContext validatorContext, String message) { @@ -124,23 +128,37 @@ private boolean checkUniqueStartIntervalNumbers(List expos return true; } - private boolean checkNoOverlapsInTimeWindow(List exposureKeys, + private boolean checkKeysCumulateEqualOrLessThanMaxRollingPeriodPerDay(List exposureKeys, ConstraintValidatorContext validatorContext) { - if (exposureKeys.size() < 2) { - return true; + + boolean isValidRollingPeriod = exposureKeys.stream().collect(Collectors + .groupingBy(TemporaryExposureKey::getRollingStartIntervalNumber, + Collectors.summingInt(TemporaryExposureKey::getRollingPeriod))) + .values().stream() + .anyMatch(sum -> sum <= maxRollingPeriod); + + if (!isValidRollingPeriod) { + addViolation(validatorContext, "The sum of the rolling periods exceeds 144 per day"); + return false; } + return true; + } + + private boolean keysHaveFlexibleRollingPeriod(List exposureKeys) { + return exposureKeys.stream() + .anyMatch(temporaryExposureKey -> temporaryExposureKey.getRollingPeriod() < maxRollingPeriod); + } - Integer[] sortedStartIntervalNumbers = exposureKeys.stream() - .mapToInt(TemporaryExposureKey::getRollingStartIntervalNumber) - .sorted().boxed().toArray(Integer[]::new); + private boolean checkStartIntervalNumberIsAtMidNight(List exposureKeys, + ConstraintValidatorContext validatorContext) { + boolean isNotMidNight00Utc = exposureKeys.stream() + .anyMatch(exposureKey -> exposureKey.getRollingStartIntervalNumber() % maxRollingPeriod > 0); - for (int i = 1; i < sortedStartIntervalNumbers.length; i++) { - if ((sortedStartIntervalNumbers[i - 1] + EXPECTED_ROLLING_PERIOD) > sortedStartIntervalNumbers[i]) { - addViolation(validatorContext, String.format( - "Subsequent intervals overlap. StartIntervalNumbers: %s", sortedStartIntervalNumbers)); - return false; - } + if (isNotMidNight00Utc) { + addViolation(validatorContext, "Start Interval Number must be at midnight ( 00:00 UTC )"); + return false; } + return true; } } diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 9b8fe59f6a..33d67e1ee2 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -25,9 +25,11 @@ services: connection-pool-size: 200 # The maximum request size accepted by the SubmissionController (e.g. 200B or 100KB). maximum-request-size: ${MAXIMUM_REQUEST_SIZE:100KB} + # The maximum rolling period accepted by the SubmissionController (e.g. 144 (24 hours)) + max-rolling-period: ${MAX_ROLLING_PERIOD:144} payload: # The maximum number of keys accepted for any submission. - max-number-of-keys: 14 + max-number-of-keys: ${MAX_NUMBER_OF_KEYS:14} verification: base-url: ${VERIFICATION_BASE_URL:http://localhost:8004} path: /version/v1/tan/verify @@ -116,4 +118,4 @@ feign: config: default: connect-timeout: 5000 - read-timeout: 5000 \ No newline at end of file + read-timeout: 5000 diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java index cbd427182a..c7dfddb411 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java @@ -20,11 +20,7 @@ package app.coronawarn.server.services.submission.controller; -import static app.coronawarn.server.services.submission.controller.RequestExecutor.VALID_KEY_DATA_1; -import static app.coronawarn.server.services.submission.controller.RequestExecutor.VALID_KEY_DATA_2; -import static app.coronawarn.server.services.submission.controller.RequestExecutor.VALID_KEY_DATA_3; -import static app.coronawarn.server.services.submission.controller.RequestExecutor.buildTemporaryExposureKey; -import static app.coronawarn.server.services.submission.controller.RequestExecutor.createRollingStartIntervalNumber; +import static app.coronawarn.server.services.submission.controller.RequestExecutor.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -36,9 +32,13 @@ import app.coronawarn.server.services.submission.verification.TanVerifier; import java.util.ArrayList; import java.util.Collection; +import java.util.List; + import org.assertj.core.util.Lists; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -67,22 +67,21 @@ void check400ResponseStatusForMissingKeys() { } @Test - void check400ResponseStatusForTooManyKeys() { + void check400ResponseStatusForTooManyKeysWithFixedRollingPeriod() { ResponseEntity actResponse = executor.executePost(buildPayloadWithTooManyKeys()); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } private Collection buildPayloadWithTooManyKeys() { ArrayList tooMany = new ArrayList<>(); for (int i = 0; i <= 20; i++) { - tooMany.add(buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2) + i * DiagnosisKey.EXPECTED_ROLLING_PERIOD , 3)); + tooMany.add(buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2) + i * DiagnosisKey.MAX_ROLLING_PERIOD , 3)); } return tooMany; } @Test - void check400ResponseStatusForDuplicateStartIntervalNumber() { + void check400ResponseStatusForKeysWithFixedRollingPeriodAndDuplicateStartIntervals() { int rollingStartIntervalNumber = createRollingStartIntervalNumber(2); var keysWithDuplicateStartIntervalNumber = Lists.list( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber, 1), @@ -94,10 +93,10 @@ void check400ResponseStatusForDuplicateStartIntervalNumber() { } @Test - void check200ResponseStatusForGapsInTimeIntervals() { + void check200ResponseStatusForGapsInTimeIntervalsOfKeysWithFixedRollingPeriod() { int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); - int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; - int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + 2 * DiagnosisKey.EXPECTED_ROLLING_PERIOD; + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.MAX_ROLLING_PERIOD; + int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + 3 * DiagnosisKey.MAX_ROLLING_PERIOD; var keysWithGapsInStartIntervalNumber = Lists.list( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3), @@ -109,30 +108,112 @@ void check200ResponseStatusForGapsInTimeIntervals() { } @Test - void check400ResponseStatusForOverlappingTimeIntervals() { + void check200ResponseStatusForGapsInTimeIntervalsOfKeysWithFlexibleRollingPeriod() { int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); - int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + (DiagnosisKey.EXPECTED_ROLLING_PERIOD / 2); - var keysWithOverlappingStartIntervalNumber = Lists.list( - buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), - buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.MAX_ROLLING_PERIOD; + int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + 3 * DiagnosisKey.MAX_ROLLING_PERIOD; + var keysWithGapsInStartIntervalNumber = Lists.list( + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1, 54), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1, 90), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3, 133), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2, 144)); + + ResponseEntity actResponse = executor.executePost(keysWithGapsInStartIntervalNumber); - ResponseEntity actResponse = executor.executePost(keysWithOverlappingStartIntervalNumber); + assertThat(actResponse.getStatusCode()).isEqualTo(OK); + } + @ParameterizedTest + @MethodSource("app.coronawarn.server.services.submission.controller.TEKDatasetGeneration#getOverlappingTestDatasets") + void check400ResponseStatusForOverlappingTimeIntervalsI(List dataset) { + ResponseEntity actResponse = executor.executePost(dataset); assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } + @ParameterizedTest + @MethodSource("app.coronawarn.server.services.submission.controller.TEKDatasetGeneration#getRollingPeriodDatasets") + void check200ResponseStatusForValidSubmissionPayload(List dataset) { + ResponseEntity actResponse = executor.executePost(dataset); + assertThat(actResponse.getStatusCode()).isEqualTo(OK); + } + + /** + * This test generates a payload with keys for the past 30 days. It verifies that validation passes even + * though keys older than application.yml/retention-period would not be stored. + */ @Test - void check200ResponseStatusForValidSubmissionPayload() { - int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); - int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; - int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; - var keysWithValidStartIntervalNumber = Lists.list( - buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), - buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3), - buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); + void check200ResponseStatusForMoreThan14KeysWithValidFlexibleRollingPeriod() { + ResponseEntity actResponse = executor.executePost(buildPayloadWithMoreThan14KeysAndFlexibleRollingPeriod()); + assertThat(actResponse.getStatusCode()).isEqualTo(OK); + } + + private Collection buildPayloadWithMoreThan14KeysAndFlexibleRollingPeriod() { + ArrayList flexibleRollingPeriodKeys = new ArrayList<>(); + /* Generate keys with fixed rolling period (144) for the past 20 days */ + for (int i = 0 ; i < 20; i++) { + flexibleRollingPeriodKeys.add(buildTemporaryExposureKey(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2) - i * DiagnosisKey.MAX_ROLLING_PERIOD, 3)); + } + /* Generate another 10 keys with flexible rolling period (<144) */ + for (int i = 20 ; i < 30; i++) { + flexibleRollingPeriodKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2) - i * DiagnosisKey.MAX_ROLLING_PERIOD, 3, 133)); + + } + return flexibleRollingPeriodKeys; + } - ResponseEntity actResponse = executor.executePost(keysWithValidStartIntervalNumber); + @Test + void check400ResponseStatusWhenTwoKeysCumulateMoreThanMaxRollingPeriodInSameDay() { + ResponseEntity actResponse = executor.executePost(buildPayloadWithKeysThatCumulateMoreThanMaxRollingPeriodPerDay()); + assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + private Collection buildPayloadWithKeysThatCumulateMoreThanMaxRollingPeriodPerDay() { + ArrayList temporaryExposureKeys = new ArrayList<>(); + temporaryExposureKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2), 3, 100)); + temporaryExposureKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2), 3, 144)); + + return temporaryExposureKeys; + } + + @Test + void check200ResponseStatusWithTwoKeysOneFlexibleAndOneDefaultOnDifferentDays() { + ResponseEntity actResponse = executor.executePost(buildPayloadWithTwoKeysOneFlexibleAndOneDefaultOnDifferentDays()); assertThat(actResponse.getStatusCode()).isEqualTo(OK); } + + private Collection buildPayloadWithTwoKeysOneFlexibleAndOneDefaultOnDifferentDays() { + ArrayList flexibleRollingPeriodKeys = new ArrayList<>(); + + flexibleRollingPeriodKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2), 3, 100)); + flexibleRollingPeriodKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(3), 3, 144)); + + + return flexibleRollingPeriodKeys; + } + + @Test + void check200ResponseStatusWhenKeysCumulateToMaxRollingPeriodInSameDay() { + ResponseEntity actResponse = executor.executePost(buildPayloadWithTwoKeysWithFlexibleRollingPeriod()); + + assertThat(actResponse.getStatusCode()).isEqualTo(OK); + } + + private Collection buildPayloadWithTwoKeysWithFlexibleRollingPeriod() { + ArrayList flexibleRollingPeriodKeys = new ArrayList<>(); + + flexibleRollingPeriodKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2), 3, 100)); + flexibleRollingPeriodKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2), 3, 44)); + + + return flexibleRollingPeriodKeys; + } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java index 32ae7308c8..74fd280f6d 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java @@ -92,6 +92,15 @@ public static TemporaryExposureKey buildTemporaryExposureKey( .setTransmissionRiskLevel(transmissionRiskLevel).build(); } + public static TemporaryExposureKey buildTemporaryExposureKeyWithFlexibleRollingPeriod( + String keyData, int rollingStartIntervalNumber, int transmissionRiskLevel, int rollingPeriod) { + return TemporaryExposureKey.newBuilder() + .setKeyData(ByteString.copyFromUtf8(keyData)) + .setRollingStartIntervalNumber(rollingStartIntervalNumber) + .setTransmissionRiskLevel(transmissionRiskLevel) + .setRollingPeriod(rollingPeriod).build(); + } + public static int createRollingStartIntervalNumber(Integer daysAgo) { return Math.toIntExact(LocalDate .ofInstant(Instant.now(), UTC) @@ -100,6 +109,6 @@ public static int createRollingStartIntervalNumber(Integer daysAgo) { } public static Collection buildPayloadWithOneKey() { - return Collections.singleton(buildTemporaryExposureKey(VALID_KEY_DATA_1, 1, 3)); + return Collections.singleton(buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(1), 3)); } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java index 66fc94e627..33b3dc0b8f 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java @@ -254,8 +254,8 @@ private SubmissionPayload buildPayloadWithTooLargePadding() { private Collection buildMultipleKeys() { int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(config.getRetentionDays() - 1); - int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; - int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.EXPECTED_ROLLING_PERIOD; + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.MAX_ROLLING_PERIOD; + int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.MAX_ROLLING_PERIOD; return Stream.of( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 3), buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber3, 6), @@ -267,7 +267,7 @@ private TemporaryExposureKey createOutdatedKey() { return TemporaryExposureKey.newBuilder() .setKeyData(ByteString.copyFromUtf8(VALID_KEY_DATA_2)) .setRollingStartIntervalNumber(createRollingStartIntervalNumber(config.getRetentionDays())) - .setRollingPeriod(DiagnosisKey.EXPECTED_ROLLING_PERIOD) + .setRollingPeriod(DiagnosisKey.MAX_ROLLING_PERIOD) .setTransmissionRiskLevel(5).build(); } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/TEKDatasetGeneration.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/TEKDatasetGeneration.java new file mode 100644 index 0000000000..7be4a19b9e --- /dev/null +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/TEKDatasetGeneration.java @@ -0,0 +1,113 @@ +/*- + * ---license-start + * Corona-Warn-App + * --- + * Copyright (C) 2020 SAP SE 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 app.coronawarn.server.services.submission.controller; + +import app.coronawarn.server.common.persistence.domain.DiagnosisKey; +import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; +import org.assertj.core.util.Lists; +import org.junit.jupiter.params.provider.Arguments; +import java.util.List; +import java.util.stream.Stream; + +import static app.coronawarn.server.services.submission.controller.RequestExecutor.*; +import static app.coronawarn.server.services.submission.controller.RequestExecutor.VALID_KEY_DATA_2; + +public class TEKDatasetGeneration { + + private static Stream getOverlappingTestDatasets() { + return Stream.of( + getOverlappingDatasetWithFixedPeriods(), + getOverlappingDatasetWithFlexiblePeriods(), + getMixedOverlappingDataset() + ).map(Arguments::of); + } + + private static List getOverlappingDatasetWithFixedPeriods(){ + int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + (DiagnosisKey.MAX_ROLLING_PERIOD / 2); + return Lists.list( + buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1), + buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); + + } + + private static List getOverlappingDatasetWithFlexiblePeriods(){ + int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + (DiagnosisKey.MAX_ROLLING_PERIOD / 2); + return Lists.list( + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1, 54), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1, 90), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2, 133)); + + } + + private static List getMixedOverlappingDataset(){ + int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + (DiagnosisKey.MAX_ROLLING_PERIOD / 2); + return Lists.list( + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1, 54), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1, 1, 90), + buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); + + } + + private static Stream getRollingPeriodDatasets() { + return Stream.of( + getFixedRollingPeriodDataset(), + getFlexibleRollingPeriodDataset(), + getMixedRollingPeriodDataset() + ).map(Arguments::of); + } + + private static List getFixedRollingPeriodDataset(){ + int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.MAX_ROLLING_PERIOD; + int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.MAX_ROLLING_PERIOD; + return Lists.list( + buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber1, 3), + buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3), + buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); + } + + private static List getFlexibleRollingPeriodDataset(){ + int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.MAX_ROLLING_PERIOD; + int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.MAX_ROLLING_PERIOD; + return Lists.list( + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1,3, 54), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1,3, 90), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_3, rollingStartIntervalNumber3,3, 133), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_3, rollingStartIntervalNumber3,2, 11), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_2, rollingStartIntervalNumber2,2, 100)); + } + + private static List getMixedRollingPeriodDataset(){ + int rollingStartIntervalNumber1 = createRollingStartIntervalNumber(6); + int rollingStartIntervalNumber2 = rollingStartIntervalNumber1 + DiagnosisKey.MAX_ROLLING_PERIOD; + int rollingStartIntervalNumber3 = rollingStartIntervalNumber2 + DiagnosisKey.MAX_ROLLING_PERIOD; + return Lists.list( + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1,3, 54), + buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, rollingStartIntervalNumber1,3, 90), + buildTemporaryExposureKey(VALID_KEY_DATA_3, rollingStartIntervalNumber3, 3), + buildTemporaryExposureKey(VALID_KEY_DATA_2, rollingStartIntervalNumber2, 2)); + } + +} diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 908e1c9208..53992cd0a9 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -26,6 +26,7 @@ services: random-key-padding-multiplier: 10 connection-pool-size: 200 maximum-request-size: 100KB + max-rolling-period: 144 payload: max-number-of-keys: 14 verification: From 5e81409fad62902edac2c1a30edf4056af26f8d8 Mon Sep 17 00:00:00 2001 From: KevponSAP <66735382+KevponSAP@users.noreply.github.com> Date: Tue, 25 Aug 2020 17:25:06 +0200 Subject: [PATCH 12/28] Feature/enrich app config (#726) * Added supported countries to app config with application.yaml parameter * Fixed Test failures * Remove unused code * Added app version parameters to application.yaml * Added tests for app version parameters * Refacted country list implementation Co-authored-by: Hilmar Falkenberg --- .../protocols/internal/app_config.proto | 2 + ...icationConfigurationPublicationConfig.java | 47 ++++++- .../config/DistributionServiceConfig.java | 66 ++++++++++ .../src/main/resources/application.yaml | 11 +- .../resources/master-config/app-config.yaml | 1 - .../master-config/app-version-config.yaml | 25 ---- ...tionVersionConfigurationValidatorTest.java | 124 +++++++++++++++--- .../test/resources/app-version/all_ok.yaml | 18 --- .../resources/app-version/broken_syntax.yaml | 18 --- .../src/test/resources/app-version/empty.yaml | 1 - .../app-version/latest-equals-min.yaml | 18 --- .../app-version/latest-higher-than-min.yaml | 18 --- .../app-version/latest-lower-than-min.yaml | 18 --- .../resources/app-version/wrong_file.yaml | 20 --- .../src/test/resources/application.yaml | 7 + .../configtests/app-config_broken_syntax.yaml | 1 - .../resources/configtests/app-config_ok.yaml | 1 - 17 files changed, 238 insertions(+), 158 deletions(-) delete mode 100644 services/distribution/src/main/resources/master-config/app-version-config.yaml delete mode 100644 services/distribution/src/test/resources/app-version/all_ok.yaml delete mode 100644 services/distribution/src/test/resources/app-version/broken_syntax.yaml delete mode 100644 services/distribution/src/test/resources/app-version/empty.yaml delete mode 100644 services/distribution/src/test/resources/app-version/latest-equals-min.yaml delete mode 100644 services/distribution/src/test/resources/app-version/latest-higher-than-min.yaml delete mode 100644 services/distribution/src/test/resources/app-version/latest-lower-than-min.yaml delete mode 100644 services/distribution/src/test/resources/app-version/wrong_file.yaml diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto index f21b7806cd..f9620498c1 100644 --- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto @@ -21,4 +21,6 @@ message ApplicationConfiguration { app.coronawarn.server.common.protocols.internal.ApplicationVersionConfiguration appVersion = 5; app.coronawarn.server.common.protocols.internal.AppFeatures appFeatures = 6; + + repeated string supportedCountries = 7; } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java index c06facaa7e..dab38047fc 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationPublicationConfig.java @@ -23,10 +23,17 @@ import app.coronawarn.server.common.protocols.internal.AppFeatures; import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration; import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration.Builder; +import app.coronawarn.server.common.protocols.internal.ApplicationVersionConfiguration; +import app.coronawarn.server.common.protocols.internal.ApplicationVersionInfo; +import app.coronawarn.server.common.protocols.internal.SemanticVersion; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppVersions; +import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + + /** * Provides the application configuration needed for the mobile client. Contains all necessary sub-configs, including: *
    @@ -54,9 +61,47 @@ public class ApplicationConfigurationPublicationConfig { @Bean public ApplicationConfiguration createMasterConfiguration(DistributionServiceConfig distributionServiceConfig) throws UnableToLoadFileException { + return YamlLoader.loadYamlIntoProtobufBuilder(MASTER_FILE, Builder.class) .setAppFeatures( - AppFeatures.newBuilder().addAllAppFeatures(distributionServiceConfig.getAppFeaturesProto()).build()) + AppFeatures.newBuilder().addAllAppFeatures(distributionServiceConfig.getAppFeaturesProto()).build() + ) + .addAllSupportedCountries(List.of(distributionServiceConfig.getSupportedCountries())) + .setAppVersion(buildApplicationVersionConfiguration(distributionServiceConfig)) + .build(); + } + + /** + * Fetches the master configuration as a ApplicationConfiguration instance. + * + * @return test. + */ + public ApplicationVersionConfiguration buildApplicationVersionConfiguration( + DistributionServiceConfig distributionServiceConfig) { + AppVersions appVersions = distributionServiceConfig.getAppVersions(); + return ApplicationVersionConfiguration.newBuilder() + .setAndroid(buildApplicationVersionInfo(appVersions.getLatestAndroid(), appVersions.getMinAndroid())) + .setIos(buildApplicationVersionInfo(appVersions.getLatestIos(), appVersions.getMinIos())) .build(); } + + private ApplicationVersionInfo buildApplicationVersionInfo(String latestVersion, String minVersion) { + return ApplicationVersionInfo.newBuilder() + .setLatest(buildSemanticVersion(latestVersion)) + .setMin(buildSemanticVersion(minVersion)) + .build(); + } + + private SemanticVersion buildSemanticVersion(String version) { + return SemanticVersion.newBuilder() + .setMajor(getSemanticVersionNumber(version, 0)) + .setMinor(getSemanticVersionNumber(version, 1)) + .setPatch(getSemanticVersionNumber(version, 2)) + .build(); + } + + private int getSemanticVersionNumber(String version, int position) { + String[] items = version.split("\\."); + return Integer.valueOf(items[position]); + } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java index e6e00582e5..f02766192f 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java @@ -21,6 +21,8 @@ package app.coronawarn.server.services.distribution.config; import app.coronawarn.server.common.protocols.external.exposurenotification.SignatureInfo; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.validation.constraints.Max; @@ -47,6 +49,7 @@ public class DistributionServiceConfig { private static final String ALGORITHM_OID_REGEX = "^[0-9]+[\\.[0-9]+]*$"; private static final String BUNDLE_REGEX = "^[a-z-]+[\\.[a-z-]+]*$"; private static final String PRIVATE_KEY_REGEX = "^(classpath:|file:[/]+)[a-zA-Z0-9_-]+[/[a-zA-Z0-9_-]+]*(.pem)?$"; + private static final String SUPPORTED_COUNTRY_CODES_REGEX = "^([a-zA-Z]{2}(\\,*[a-zA-Z]{2})*)$"; private Paths paths; private TestData testData; @@ -71,6 +74,9 @@ public class DistributionServiceConfig { private Api api; private ObjectStore objectStore; private List appFeatures; + @Pattern(regexp = SUPPORTED_COUNTRY_CODES_REGEX) + private String supportedCountries; + private AppVersions appVersions; public Paths getPaths() { return paths; @@ -185,8 +191,26 @@ public void setAppFeatures(List appFeatures) { this.appFeatures = appFeatures; } + public String[] getSupportedCountries() { + return supportedCountries.split(","); + } + + public void setSupportedCountries(String supportedCountries) { + this.supportedCountries = supportedCountries; + } + + public AppVersions getAppVersions() { + return appVersions; + } + + public void setAppVersions(AppVersions appVersions) { + this.appVersions = appVersions; + } + + /** * Get app features as list of protobuf objects. + * * @return list of {@link app.coronawarn.server.common.protocols.internal.AppFeature} */ public List getAppFeaturesProto() { @@ -583,4 +607,46 @@ public void setValue(Integer value) { this.value = value; } } + + public static class AppVersions { + + private String latestIos; + private String minIos; + private String latestAndroid; + private String minAndroid; + + + public String getLatestIos() { + return latestIos; + } + + public void setLatestIos(String latestIos) { + this.latestIos = latestIos; + } + + public String getMinIos() { + return minIos; + } + + public void setMinIos(String minIos) { + this.minIos = minIos; + } + + public String getLatestAndroid() { + return latestAndroid; + } + + public void setLatestAndroid(String latestAndroid) { + this.latestAndroid = latestAndroid; + } + + public String getMinAndroid() { + return minAndroid; + } + + public void setMinAndroid(String minAndroid) { + this.minAndroid = minAndroid; + } + + } } diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index bf0dae2185..b0549c5544 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -83,8 +83,14 @@ services: force-update-keyfiles: ${FORCE_UPDATE_KEYFILES:false} app-features: - - label: isPlausibleDeniabilityActive - value: ${PLAUSIBLE_DENIABILITY_ACTIVE:1} + - label: reserved + value: ${reserved:1} + supported-countries: ${SUPPORTED_COUNTRIES:DE} + app-versions: + latest-ios: ${LATEST_IOS_VERSION:0.8.2} + min-ios: ${MIN_IOS_VERSION:0.5.0} + latest-android: ${LATEST_IOS_VERSION:1.0.4} + min-android: ${MIN_ANDROID_VERSION:1.0.4} spring: main: @@ -98,6 +104,7 @@ spring: datasource: driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE}?ssl=true&sslmode=verify-full&sslrootcert=${SSL_POSTGRES_CERTIFICATE_PATH}&sslcert=${SSL_DISTRIBUTION_CERTIFICATE_PATH}&sslkey=${SSL_DISTRIBUTION_PRIVATE_KEY_PATH} username: ${POSTGRESQL_USER_DISTRIBUTION:local_setup_distribution} password: ${POSTGRESQL_PASSWORD_DISTRIBUTION:local_setup_distribution} diff --git a/services/distribution/src/main/resources/master-config/app-config.yaml b/services/distribution/src/main/resources/master-config/app-config.yaml index 79ae35f865..5e401598a6 100644 --- a/services/distribution/src/main/resources/master-config/app-config.yaml +++ b/services/distribution/src/main/resources/master-config/app-config.yaml @@ -14,4 +14,3 @@ min-risk-score: 11 attenuation-duration: !include attenuation-duration.yaml risk-score-classes: !include risk-score-classification.yaml exposure-config: !include exposure-config.yaml -app-version: !include app-version-config.yaml diff --git a/services/distribution/src/main/resources/master-config/app-version-config.yaml b/services/distribution/src/main/resources/master-config/app-version-config.yaml deleted file mode 100644 index 515a7841f8..0000000000 --- a/services/distribution/src/main/resources/master-config/app-version-config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# This is the Application Version Configuration master file which contains information for clients -# about the latest and minimum supported mobile app versions on Android and iOS. -# -# The latest version must not be lower than the min version. -# -# Change this file with caution! - -ios: - latest: - major: 0 - minor: 8 - patch: 2 - min: - major: 0 - minor: 5 - patch: 0 -android: - latest: - major: 1 - minor: 0 - patch: 4 - min: - major: 1 - minor: 0 - patch: 4 diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationVersionConfigurationValidatorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationVersionConfigurationValidatorTest.java index c75e0a9144..655963ba3f 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationVersionConfigurationValidatorTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationVersionConfigurationValidatorTest.java @@ -26,36 +26,128 @@ import static org.assertj.core.api.Assertions.assertThat; import app.coronawarn.server.common.protocols.internal.ApplicationVersionConfiguration; -import app.coronawarn.server.services.distribution.assembly.appconfig.UnableToLoadFileException; -import app.coronawarn.server.services.distribution.assembly.appconfig.YamlLoader; +import app.coronawarn.server.services.distribution.assembly.appconfig.ApplicationConfigurationPublicationConfig; import app.coronawarn.server.services.distribution.assembly.appconfig.validation.ValidationError.ErrorType; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.util.stream.Stream; + +@EnableConfigurationProperties(value = DistributionServiceConfig.class) +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {DistributionServiceConfig.class,ApplicationConfigurationPublicationConfig.class}, + initializers = ConfigFileApplicationContextInitializer.class) class ApplicationVersionConfigurationValidatorTest { private static final ValidationResult SUCCESS = new ValidationResult(); - @Test - void succeedsIfLatestEqualsMin() throws UnableToLoadFileException { - var validator = buildValidator("app-version/latest-equals-min.yaml"); + private ConfigurationValidator buildValidator(DistributionServiceConfig distributionServiceConfig) { + ApplicationVersionConfiguration appConfig = applicationConfigurationPublicationConfig + .buildApplicationVersionConfiguration(distributionServiceConfig); + return new ApplicationVersionConfigurationValidator(appConfig); + } + + @Autowired + DistributionServiceConfig distributionServiceConfig; + + @Autowired + ApplicationConfigurationPublicationConfig applicationConfigurationPublicationConfig; + + @ParameterizedTest + @MethodSource("setSemanticVersionsLatestHigherThanMin") + void succeedsIfLatestHigherThanMin(String latestAndroid, String minAndroid, String latestIos, String minIos) { + distributionServiceConfig.getAppVersions().setLatestAndroid(latestAndroid); + distributionServiceConfig.getAppVersions().setMinAndroid(minAndroid); + distributionServiceConfig.getAppVersions().setLatestIos(latestIos); + distributionServiceConfig.getAppVersions().setMinIos(minIos); + + var validator = buildValidator(distributionServiceConfig); assertThat(validator.validate()).isEqualTo(SUCCESS); } + private static Stream setSemanticVersionsLatestHigherThanMin() { + return Stream.of( + Arguments.of("2.0.0", "1.0.0", "1.0.0", "1.0.0"), + Arguments.of("0.2.0", "0.1.0", "1.0.0", "1.0.0"), + Arguments.of("0.0.2", "0.0.1", "1.0.0", "1.0.0"), + Arguments.of("1.0.0", "1.0.0", "2.0.0", "1.0.0"), + Arguments.of("1.0.0", "1.0.0", "0.2.0", "0.1.0"), + Arguments.of("1.0.0", "1.0.0", "0.0.2", "0.0.1") + ); + } + + @ParameterizedTest + @MethodSource("setSemanticVersionsLatestEqualsMin") + void succeedsWithEqualSemanticVersion(String latestAndroid, String minAndroid, String latestIos, String minIos) { - @Test - void succeedsIfLatestHigherThanMin() throws UnableToLoadFileException { - var validator = buildValidator("app-version/latest-higher-than-min.yaml"); + distributionServiceConfig.getAppVersions().setLatestAndroid(latestAndroid); + distributionServiceConfig.getAppVersions().setMinAndroid(minAndroid); + distributionServiceConfig.getAppVersions().setLatestIos(latestIos); + distributionServiceConfig.getAppVersions().setMinIos(minIos); + + var validator = buildValidator(distributionServiceConfig); assertThat(validator.validate()).isEqualTo(SUCCESS); } - @Test - void failsIfLatestLowerThanMin() throws UnableToLoadFileException { - var validator = buildValidator("app-version/latest-lower-than-min.yaml"); - assertThat(validator.validate()).isEqualTo( - buildExpectedResult(buildError(CONFIG_PREFIX + "ios.[latest|min]", "1.2.2", ErrorType.MIN_GREATER_THAN_MAX))); + private static Stream setSemanticVersionsLatestEqualsMin() { + return Stream.of( + Arguments.of("1.0.0", "1.0.0", "1.0.0", "1.0.0"), + Arguments.of("0.1.0", "0.1.0", "1.0.0", "1.0.0"), + Arguments.of("0.0.1", "0.0.1", "1.0.0", "1.0.0"), + Arguments.of("1.0.0", "1.0.0", "1.0.0", "1.0.0"), + Arguments.of("1.0.0", "1.0.0", "0.1.0", "0.1.0"), + Arguments.of("1.0.0", "1.0.0", "0.0.1", "0.0.1") + ); + } + + @ParameterizedTest + @MethodSource("setSemanticVersionsLatestLowerThanMinAndroid") + void failsWithBadSemanticVersionAndroid(String latestAndroid, String minAndroid, String latestIos, String minIos) { + + distributionServiceConfig.getAppVersions().setLatestAndroid(latestAndroid); + distributionServiceConfig.getAppVersions().setMinAndroid(minAndroid); + distributionServiceConfig.getAppVersions().setLatestIos(latestIos); + distributionServiceConfig.getAppVersions().setMinIos(minIos); + + var validator = buildValidator(distributionServiceConfig); + + assertThat(validator.validate()).isEqualTo(buildExpectedResult(buildError(CONFIG_PREFIX + "android.[latest|min]", minAndroid, ErrorType.MIN_GREATER_THAN_MAX))); + } + private static Stream setSemanticVersionsLatestLowerThanMinAndroid() { + return Stream.of( + Arguments.of("1.0.0", "2.0.0", "1.0.0", "1.0.0"), + Arguments.of("1.0.0", "1.1.0", "1.0.0", "1.0.0"), + Arguments.of("1.0.0", "1.0.1", "1.0.0", "1.0.0") + ); + } + + @ParameterizedTest + @MethodSource("setSemanticVersionsLatestLowerThanMinIos") + void failsWithBadSemanticVersionIos(String latestAndroid, String minAndroid, String latestIos, String minIos) { + + distributionServiceConfig.getAppVersions().setLatestAndroid(latestAndroid); + distributionServiceConfig.getAppVersions().setMinAndroid(minAndroid); + distributionServiceConfig.getAppVersions().setLatestIos(latestIos); + distributionServiceConfig.getAppVersions().setMinIos(minIos); + + var validator = buildValidator(distributionServiceConfig); + + assertThat(validator.validate()).isEqualTo(buildExpectedResult(buildError(CONFIG_PREFIX + "ios.[latest|min]", minIos, ErrorType.MIN_GREATER_THAN_MAX))); } + private static Stream setSemanticVersionsLatestLowerThanMinIos() { + return Stream.of( - private ConfigurationValidator buildValidator(String filePath) throws UnableToLoadFileException { - var configBuilder = YamlLoader.loadYamlIntoProtobufBuilder(filePath, ApplicationVersionConfiguration.Builder.class); - return new ApplicationVersionConfigurationValidator(configBuilder.build()); + Arguments.of("1.0.0", "1.0.0", "1.0.0", "2.0.0"), + Arguments.of("1.0.0", "1.0.0", "1.0.0", "1.1.0"), + Arguments.of("1.0.0", "1.0.0", "1.0.0", "1.0.1") + ); } } diff --git a/services/distribution/src/test/resources/app-version/all_ok.yaml b/services/distribution/src/test/resources/app-version/all_ok.yaml deleted file mode 100644 index c28b2eb2fa..0000000000 --- a/services/distribution/src/test/resources/app-version/all_ok.yaml +++ /dev/null @@ -1,18 +0,0 @@ -ios: - latest: - major: 1 - minor: 5 - patch: 2 - min: - major: 1 - minor: 2 - patch: 2 -android: - latest: - major: 1 - minor: 3 - patch: 2 - min: - major: 1 - minor: 1 - patch: 2 diff --git a/services/distribution/src/test/resources/app-version/broken_syntax.yaml b/services/distribution/src/test/resources/app-version/broken_syntax.yaml deleted file mode 100644 index 5dfab205ee..0000000000 --- a/services/distribution/src/test/resources/app-version/broken_syntax.yaml +++ /dev/null @@ -1,18 +0,0 @@ -ios: - latest: - major: 1 # wrong syntax, indent too high - minor: 5 - patch: 2 - min: - major: 1 - minor: 2 - patch: 2 -android: - latest: - major: 1 - minor: 3 - patch: 2 - min: - major: 1 - minor: 1 - patch: 2 diff --git a/services/distribution/src/test/resources/app-version/empty.yaml b/services/distribution/src/test/resources/app-version/empty.yaml deleted file mode 100644 index ab2fc5dd55..0000000000 --- a/services/distribution/src/test/resources/app-version/empty.yaml +++ /dev/null @@ -1 +0,0 @@ -# empty file \ No newline at end of file diff --git a/services/distribution/src/test/resources/app-version/latest-equals-min.yaml b/services/distribution/src/test/resources/app-version/latest-equals-min.yaml deleted file mode 100644 index cd0acc0aaa..0000000000 --- a/services/distribution/src/test/resources/app-version/latest-equals-min.yaml +++ /dev/null @@ -1,18 +0,0 @@ -ios: - latest: - major: 1 - minor: 2 - patch: 2 - min: - major: 1 - minor: 2 - patch: 2 -android: - latest: - major: 1 - minor: 3 - patch: 2 - min: - major: 1 - minor: 3 - patch: 2 diff --git a/services/distribution/src/test/resources/app-version/latest-higher-than-min.yaml b/services/distribution/src/test/resources/app-version/latest-higher-than-min.yaml deleted file mode 100644 index c43757881b..0000000000 --- a/services/distribution/src/test/resources/app-version/latest-higher-than-min.yaml +++ /dev/null @@ -1,18 +0,0 @@ -ios: - latest: - major: 1 - minor: 2 - patch: 3 - min: - major: 1 - minor: 2 - patch: 2 -android: - latest: - major: 1 - minor: 3 - patch: 2 - min: - major: 1 - minor: 3 - patch: 2 diff --git a/services/distribution/src/test/resources/app-version/latest-lower-than-min.yaml b/services/distribution/src/test/resources/app-version/latest-lower-than-min.yaml deleted file mode 100644 index 95ff915d67..0000000000 --- a/services/distribution/src/test/resources/app-version/latest-lower-than-min.yaml +++ /dev/null @@ -1,18 +0,0 @@ -ios: - latest: - major: 1 - minor: 2 - patch: 1 - min: - major: 1 - minor: 2 - patch: 2 -android: - latest: - major: 1 - minor: 3 - patch: 2 - min: - major: 1 - minor: 3 - patch: 2 diff --git a/services/distribution/src/test/resources/app-version/wrong_file.yaml b/services/distribution/src/test/resources/app-version/wrong_file.yaml deleted file mode 100644 index 27151279a1..0000000000 --- a/services/distribution/src/test/resources/app-version/wrong_file.yaml +++ /dev/null @@ -1,20 +0,0 @@ -ID: com.acme.mta.sample -version: 1.0.1 -modules: - - name: pricing-ui - type: nodejs - path: ./ui - requires: - - name: thedatabase - - - name: pricing-backend - type: java - path: ./backend - provides: - - name: price_opt - properties: - protocol: http - uri: myhost.mydomain -resources: - - name: thedatabase - type: com.sap.xs.hdi-container \ No newline at end of file diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 62cd028177..d0a1f33500 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -56,6 +56,13 @@ services: app-features: - label: isPlausibleDeniabilityActive value: ${PLAUSIBLE_DENIABILITY_ACTIVE:1} + supported-countries: ${SUPPORTED_COUNTRIES:DE} + app-versions: + latest-ios: ${LATEST_IOS_VERSION:0.8.2} + min-ios: ${MIN_IOS_VERSION:0.5.0} + latest-android: ${LATEST_IOS_VERSION:1.0.4} + min-android: ${MIN_ANDROID_VERSION:1.0.4} + spring: main: banner-mode: off diff --git a/services/distribution/src/test/resources/configtests/app-config_broken_syntax.yaml b/services/distribution/src/test/resources/configtests/app-config_broken_syntax.yaml index 3190f78838..300e3912ce 100644 --- a/services/distribution/src/test/resources/configtests/app-config_broken_syntax.yaml +++ b/services/distribution/src/test/resources/configtests/app-config_broken_syntax.yaml @@ -3,4 +3,3 @@ min-risk-score: attenuation-duration: !include attenuation-duration_ok.yaml risk-score-classes: !include risk-score-class_ok.yaml # illegal indentation exposure-config: !include exposure-config_ok.yaml -app-version: !include app-version-config_ok.yaml diff --git a/services/distribution/src/test/resources/configtests/app-config_ok.yaml b/services/distribution/src/test/resources/configtests/app-config_ok.yaml index be28abc0d8..7465a09cfe 100644 --- a/services/distribution/src/test/resources/configtests/app-config_ok.yaml +++ b/services/distribution/src/test/resources/configtests/app-config_ok.yaml @@ -2,4 +2,3 @@ min-risk-score: 72 attenuation-duration: !include attenuation-duration_ok.yaml risk-score-classes: !include risk-score-class_ok.yaml exposure-config: !include exposure-config_ok.yaml -app-version: !include app-version-config_ok.yaml From a4d90dddd6fb10ec95de90d6be6065a9dc021e85 Mon Sep 17 00:00:00 2001 From: Sorin Stefan Iovita Date: Thu, 10 Sep 2020 17:14:59 +0300 Subject: [PATCH 13/28] Fix Sonar issues (#754) --- .../config/DistributionServiceConfig.java | 2 -- .../objectstore/publish/LocalFile.java | 6 +++--- .../controller/ApiExceptionHandler.java | 10 +++++++--- .../integration/SubmissionPersistenceIT.java | 17 ++++++++--------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java index f02766192f..ddf943c1ee 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfig.java @@ -21,8 +21,6 @@ package app.coronawarn.server.services.distribution.config; import app.coronawarn.server.common.protocols.external.exposurenotification.SignatureInfo; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.validation.constraints.Max; diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/publish/LocalFile.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/publish/LocalFile.java index bc830d67a2..fead8b1493 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/publish/LocalFile.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/objectstore/publish/LocalFile.java @@ -84,12 +84,12 @@ private String loadChecksum() { protected String createS3Key(Path file, Path rootFolder) { Path relativePath = rootFolder.relativize(file); - return relativePath.toString().replaceAll("\\\\", "/"); + return relativePath.toString().replace("\\\\", "/"); } /** * Value for the content-type header. - * + * * @return Either zip or * json. */ @@ -108,7 +108,7 @@ public String getContentType() { /** * Indicates if a local file is a Key-file or not. Only the Key files are stored in the Date / Hour tree structure. * One file per sub-folder (days: 1-31 / hours: 0-23). The index files are not stored in folders ending with a digit. - * + * * @return true if and only if the {@link #s3Key} ends with a digit, false otherwise. */ public boolean isKeyFile() { diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/ApiExceptionHandler.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/ApiExceptionHandler.java index ff7287b80d..6aec083ab1 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/ApiExceptionHandler.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/ApiExceptionHandler.java @@ -41,19 +41,23 @@ public class ApiExceptionHandler { @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public void unknownException(Exception ex, WebRequest wr) { - logger.error("Unable to handle {}", wr.getDescription(false), ex); + logger.error("Unable to handle {}", getFormattedDescription(wr), ex); } @ExceptionHandler({HttpMessageNotReadableException.class, ServletRequestBindingException.class, InvalidProtocolBufferException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public void bindingExceptions(Exception ex, WebRequest wr) { - logger.error("Binding failed {}", wr.getDescription(false), ex); + logger.error("Binding failed {}", getFormattedDescription(wr), ex); } @ExceptionHandler({InvalidDiagnosisKeyException.class, ConstraintViolationException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public void diagnosisKeyExceptions(Exception ex, WebRequest wr) { - logger.error("Erroneous Submission Payload {}", wr.getDescription(false), ex); + logger.error("Erroneous Submission Payload {}", getFormattedDescription(wr), ex); + } + + private String getFormattedDescription(WebRequest wr) { + return wr.getDescription(false).replaceAll("[\n|\r|\t]", "_"); } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java index 65527f87fd..2bf6bb403d 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/integration/SubmissionPersistenceIT.java @@ -57,16 +57,15 @@ import app.coronawarn.server.services.submission.verification.TanVerifier; /** - * This test serves more like a dev tool which helps with debugging production issues. - * It inserts keys parsed from a proto buf file whos content was captured by the mobile - * client during requests to the server. The content of the current test resource file - * can be quickly replaced during the investigation of an issue. + * This test serves more like a dev tool which helps with debugging production issues. It inserts keys parsed from a + * proto buf file whos content was captured by the mobile client during requests to the server. The content of the + * current test resource file can be quickly replaced during the investigation of an issue. */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles({ "disable-ssl-client-verification", "disable-ssl-client-verification-verify-hostname" }) +@ActiveProfiles({"disable-ssl-client-verification", "disable-ssl-client-verification-verify-hostname"}) @Sql(scripts = {"classpath:db/clean_db_state.sql"}, - executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) -public class SubmissionPersistenceIT { + executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +class SubmissionPersistenceIT { private static final Logger logger = LoggerFactory.getLogger(SubmissionPersistenceIT.class); @@ -97,8 +96,8 @@ public void setUpMocks() { @Disabled("Because the content of the .pb file becomes old and retention time passes, this test will fail. " + "Enable when debugging of a new payload is required.") @ParameterizedTest - @ValueSource(strings = { "src/test/resources/payload/mobile-client-payload.pb" }) - public void testKeyInsertionWithMobileClientProtoBuf(String testFile) throws IOException { + @ValueSource(strings = {"src/test/resources/payload/mobile-client-payload.pb"}) + void testKeyInsertionWithMobileClientProtoBuf(String testFile) throws IOException { Path path = Paths.get(testFile); InputStream input = new FileInputStream(path.toFile()); SubmissionPayload payload = SubmissionPayload.parseFrom(input); From aa0c624d1c42c555878efa5e42125dc8dd32a9f6 Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Fri, 11 Sep 2020 12:08:19 +0300 Subject: [PATCH 14/28] Bugfix/submission tek rolling period (#752) * Replaced possible variable rolling period with constant value * Add tests for flexible rolling period * apply sugested review changes * Add javaDoc comments for changed getExpiryDateTime * Fix midnight check in SubmissionPayload Validator Co-authored-by: Michael Burwig --- .../ProdDiagnosisKeyBundler.java | 3 +- ...odDiagnosisKeyBundlerExpiryPolicyTest.java | 43 +++++++++++++++++++ .../services/distribution/common/Helpers.java | 12 ++++++ .../validation/ValidSubmissionPayload.java | 3 +- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java index 72d5d8e956..2edcf433ef 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java @@ -98,11 +98,12 @@ private static Optional getEarliestDistributableTimestamp( /** * Returns the end of the rolling time window that a {@link DiagnosisKey} was active for as a {@link LocalDateTime}. + * The ".plusDays(1L)" is used as there can be now diagnosis keys with rollingPeriod set to less than 1 day. */ private LocalDateTime getExpiryDateTime(DiagnosisKey diagnosisKey) { return LocalDateTime .ofEpochSecond(diagnosisKey.getRollingStartIntervalNumber() * TEN_MINUTES_INTERVAL_SECONDS, 0, UTC) - .plusMinutes(diagnosisKey.getRollingPeriod() * 10L); + .plusDays(1L); } /** diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java index fb0bb70767..b98a0c22e4 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java @@ -85,4 +85,47 @@ void testLastPeriodOfHourAndSubmissionGreaterDistributionDateTime() { bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); } + + @ParameterizedTest + @ValueSource(longs = {0L, 24L, 24L + 2L}) + void testLastPeriodOfHourAndSubmissionLessThanDistributionDateTimeWithFlexibleRollingPeriod( + long submissionTimestamp) { + List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, submissionTimestamp, 5, 44); + diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, submissionTimestamp, 5, 100)); + bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); + assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10); + } + + @Test + void testLastPeriodOfHourAndSubmissionEqualsDistributionDateTimeWithFlexibleRollingPeriod() { + List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 3L, 5, 44); + diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 3L, 5, 100)); + bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); + assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10); + } + + @ParameterizedTest + @ValueSource(longs = {0L, 24L, 24L + 2L, 24L + 3L}) + void testFirstPeriodOfHourAndSubmissionLessThanDistributionDateTimeWithFlexibleRollingPeriod(long submissionTimestamp) { + List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, submissionTimestamp, 5, 44); + diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, submissionTimestamp, 5, 100)); + bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); + assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); + } + + @Test + void testFirstPeriodOfHourAndSubmissionEqualsDistributionDateTimeWithFlexibleRollingPeriod() { + List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, 24L + 4L, 5, 44); + diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, 24L + 4L, 5, 100)); + bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); + assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); + } + + @Test + void testLastPeriodOfHourAndSubmissionGreaterDistributionDateTimeWithFlexibleRollingPeriod() { + List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 4L, 5, 44); + diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 4L, 5, 80)); + bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); + assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); + } } diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java index d290e0ab01..1c6c047d91 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java @@ -76,6 +76,18 @@ public static List buildDiagnosisKeys(int startIntervalNumber, lon .collect(Collectors.toList()); } + public static List buildDiagnosisKeysWithFlexibleRollingPeriod( + int startIntervalNumber, long submissionTimestamp, int number, int rollingPeriod) { + return IntStream.range(0, number) + .mapToObj(ignoredValue -> DiagnosisKey.builder() + .withKeyData(new byte[16]) + .withRollingStartIntervalNumber(startIntervalNumber) + .withTransmissionRiskLevel(2) + .withSubmissionTimestamp(submissionTimestamp) + .withRollingPeriod(rollingPeriod).build()) + .collect(Collectors.toList()); + } + public static Set getFilePaths(java.io.File root, String basePath) { Set files = Arrays.stream(Objects.requireNonNull(root.listFiles())) .filter(File::isFile) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java index b5d0cfe172..858c6ef601 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java @@ -151,8 +151,9 @@ private boolean keysHaveFlexibleRollingPeriod(List exposur private boolean checkStartIntervalNumberIsAtMidNight(List exposureKeys, ConstraintValidatorContext validatorContext) { + // check if any start interval number is not set to midnight by performing modulo 24 hrs in minutes/10 boolean isNotMidNight00Utc = exposureKeys.stream() - .anyMatch(exposureKey -> exposureKey.getRollingStartIntervalNumber() % maxRollingPeriod > 0); + .anyMatch(exposureKey -> exposureKey.getRollingStartIntervalNumber() % 144 > 0); if (isNotMidNight00Utc) { addViolation(validatorContext, "Start Interval Number must be at midnight ( 00:00 UTC )"); From 3ef4ecfe1d892878a1861750c6b68f7173da35f3 Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Fri, 11 Sep 2020 12:24:07 +0300 Subject: [PATCH 15/28] Feature/update risk score master (#758) * Update risk-score-classification.yaml Problem in iOS 13.7 --> Unbekanntes Risiko bei Risikobegegnung * Adjust tests Co-authored-by: Hilmar Falkenberg Co-authored-by: Michael Burwig --- .../assembly/appconfig/validation/ParameterSpec.java | 2 +- .../resources/master-config/risk-score-classification.yaml | 6 +++--- .../src/test/resources/configtests/app-config_mrs_oob.yaml | 2 +- .../src/test/resources/configtests/risk-score-class_ok.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java index e405576076..2c9252c16a 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java @@ -52,7 +52,7 @@ private ParameterSpec() { /** * The allowed maximum value for a risk score. */ - public static final int RISK_SCORE_MAX = 72; + public static final int RISK_SCORE_MAX = 9999; /** * The allowed minimum value for an attenuation threshold. diff --git a/services/distribution/src/main/resources/master-config/risk-score-classification.yaml b/services/distribution/src/main/resources/master-config/risk-score-classification.yaml index ba5139fe89..d5b952efed 100644 --- a/services/distribution/src/main/resources/master-config/risk-score-classification.yaml +++ b/services/distribution/src/main/resources/master-config/risk-score-classification.yaml @@ -1,5 +1,5 @@ # This is the Risk Score Classification master file. It partitions the risk -# score value range (0.000 - 72.000) into 2 distinct risk classes. These are +# score value range (0.000 - 9999.000) into 2 distinct risk classes. These are # used by the respective app in order to present the correct risk classification # in a user friendly manner, based on the underlying total risk score. # @@ -9,7 +9,7 @@ # - "HIGH" class: [min, max] # # The risk classes must not overlap and -# cover the full risk score value range (0.000 - 72.000). +# cover the full risk score value range (0.000 - 9999.000). # # Change this file with caution! @@ -22,5 +22,5 @@ risk-classes: - label: HIGH min: 15 - max: 72 + max: 9999 url: "https://www.coronawarn.app" diff --git a/services/distribution/src/test/resources/configtests/app-config_mrs_oob.yaml b/services/distribution/src/test/resources/configtests/app-config_mrs_oob.yaml index 7fd514bece..91509b4a88 100644 --- a/services/distribution/src/test/resources/configtests/app-config_mrs_oob.yaml +++ b/services/distribution/src/test/resources/configtests/app-config_mrs_oob.yaml @@ -1,3 +1,3 @@ -min-risk-score: 73 +min-risk-score: 10000 risk-score-classes: !include risk-score-class_ok.yaml exposure-config: !include exposure-config_ok.yaml diff --git a/services/distribution/src/test/resources/configtests/risk-score-class_ok.yaml b/services/distribution/src/test/resources/configtests/risk-score-class_ok.yaml index 60521e44b9..2e5f06c875 100644 --- a/services/distribution/src/test/resources/configtests/risk-score-class_ok.yaml +++ b/services/distribution/src/test/resources/configtests/risk-score-class_ok.yaml @@ -7,5 +7,5 @@ risk-classes: - label: HIGH min: 15 - max: 72 + max: 9999 url: "https://www.coronawarn.app" From 8c7f6ebf49f723f56b84ff22a9dc443e013a988f Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:59:43 +0300 Subject: [PATCH 16/28] Update exposure-config.yaml (#765) patch transmission as requested by Thomas Augsten Co-authored-by: Hilmar Falkenberg --- .../src/main/resources/master-config/exposure-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/distribution/src/main/resources/master-config/exposure-config.yaml b/services/distribution/src/main/resources/master-config/exposure-config.yaml index b22a10301e..f2c4005bfb 100644 --- a/services/distribution/src/main/resources/master-config/exposure-config.yaml +++ b/services/distribution/src/main/resources/master-config/exposure-config.yaml @@ -18,8 +18,8 @@ days_weight: 20 # Parameters Section transmission: - app_defined_1: 1 - app_defined_2: 2 + app_defined_1: 0 + app_defined_2: 0 app_defined_3: 3 app_defined_4: 4 app_defined_5: 5 From f3d66840ef59e2ced10fb9b42f08a8938e76075a Mon Sep 17 00:00:00 2001 From: Sorin Stefan Iovita Date: Tue, 15 Sep 2020 16:22:32 +0300 Subject: [PATCH 17/28] Update attenuation-duration.yaml (#775) (#776) * Update attenuation-duration.yaml * Update exposure-config.yaml attenuation array = 2 Co-authored-by: Hilmar Falkenberg --- .../master-config/attenuation-duration.yaml | 2 +- .../resources/master-config/exposure-config.yaml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/services/distribution/src/main/resources/master-config/attenuation-duration.yaml b/services/distribution/src/main/resources/master-config/attenuation-duration.yaml index 7a0aa7e478..f283dea45b 100644 --- a/services/distribution/src/main/resources/master-config/attenuation-duration.yaml +++ b/services/distribution/src/main/resources/master-config/attenuation-duration.yaml @@ -17,4 +17,4 @@ weights: mid: 0.5 high: 0.0 default-bucket-offset: 0 -risk-score-normalization-divisor: 25 +risk-score-normalization-divisor: 50 diff --git a/services/distribution/src/main/resources/master-config/exposure-config.yaml b/services/distribution/src/main/resources/master-config/exposure-config.yaml index f2c4005bfb..303cb52010 100644 --- a/services/distribution/src/main/resources/master-config/exposure-config.yaml +++ b/services/distribution/src/main/resources/master-config/exposure-config.yaml @@ -49,10 +49,10 @@ days_since_last_exposure: attenuation: gt_73_dbm: 0 - gt_63_le_73_dbm: 1 - gt_51_le_63_dbm: 1 - gt_33_le_51_dbm: 1 - gt_27_le_33_dbm: 1 - gt_15_le_27_dbm: 1 - gt_10_le_15_dbm: 1 - le_10_dbm: 1 + gt_63_le_73_dbm: 2 + gt_51_le_63_dbm: 2 + gt_33_le_51_dbm: 2 + gt_27_le_33_dbm: 2 + gt_15_le_27_dbm: 2 + gt_10_le_15_dbm: 2 + le_10_dbm: 2 From af621f5774b2e23058d551453f9cbe5309d85821 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 18 Sep 2020 09:35:55 +0200 Subject: [PATCH 18/28] prepare release 1.5.0 (#793) --- .mvn/maven.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index 6147f32101..fa5b5afa43 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,4 +1,4 @@ --Drevision=1.4.0-SNAPSHOT +-Drevision=1.5.0-SNAPSHOT -Dlicense.projectName=Corona-Warn-App -Dlicense.inceptionYear=2020 -Dlicense.licenseName=apache_v2 From db7be7f2934878235133a4b0811a124305f562b2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 23 Sep 2020 12:55:03 +0200 Subject: [PATCH 19/28] Fix sping web guava (#808) * RC 1.4.0 * mitigate CVE-2020-5421 by switching to newer spring-boot-starter-parent (#796) * Release/1.4 log submission keys (#797) * Implement the changes from PR #783 * Add check and logs for missing key with TRL 6 or having a key TRL 6 from today midnight * Fix code smell * ensure usage of latest guava version 29.0-jre (#806) Co-authored-by: Michael <66735191+mibrasap@users.noreply.github.com> --- common/persistence/pom.xml | 2 +- services/pom.xml | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index d378847fc5..833e2ca857 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.2.RELEASE + 2.3.4.RELEASE 4.0.0 diff --git a/services/pom.xml b/services/pom.xml index 507e7bcbe7..8b992a9a5b 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.2.RELEASE + 2.3.4.RELEASE @@ -35,6 +35,16 @@ corona-warn-app_cwa-server_services + + + + com.google.guava + guava + 29.0-jre + + + + org.springframework.cloud From f7dc00cebd97922007f16f091ccc58b1cf936b90 Mon Sep 17 00:00:00 2001 From: Michael Burwig <64439292+michael-burwig@users.noreply.github.com> Date: Thu, 24 Sep 2020 16:23:52 +0200 Subject: [PATCH 20/28] Update CODEOWNERS file (#817) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 221b300b2c..6d6a5e5515 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,4 +5,4 @@ # For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. # These are the default owners for the whole content of this repository. The default owners are automatically added as reviewers when you open a pull request, unless different owners are specified in the file. -* @christian-kirschnick @johanneseschrig @michael-burwig @pithumke @ole-lilienthal @MKusber @stevesap @UnchartedBull @hilmarf @sorin-stefan-iovita-sap @KevponSAP @EvgeniiSkrebtcov @ioangut @kilbphilippSAP @emmetsap @EugenM-SAP @fredrb +* @hilmarf @sorin-stefan-iovita-sap @KevponSAP @EvgeniiSkrebtcov @ioangut @kilbphilippSAP @emmetsap @EugenM-SAP @fredrb From 8aad1e121a6367be5157c3aa33a0fbaed0d8943d Mon Sep 17 00:00:00 2001 From: Michael <66735191+mibrasap@users.noreply.github.com> Date: Fri, 25 Sep 2020 08:13:39 +0200 Subject: [PATCH 21/28] Admin/update codeowners (#818) * Update CODEOWNERS file * Add additional team members to codeowners file Co-authored-by: Michael Burwig --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6d6a5e5515..0fb315b1f2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,4 +5,4 @@ # For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. # These are the default owners for the whole content of this repository. The default owners are automatically added as reviewers when you open a pull request, unless different owners are specified in the file. -* @hilmarf @sorin-stefan-iovita-sap @KevponSAP @EvgeniiSkrebtcov @ioangut @kilbphilippSAP @emmetsap @EugenM-SAP @fredrb +* @hilmarf @sorin-stefan-iovita-sap @KevponSAP @EvgeniiSkrebtcov @ioangut @kilbphilippSAP @emmetsap @EugenM-SAP @fredrb @mibrasap @JonasZeitzem-sap From 9cfd6d3974754ee54179ae64a2063ea0dd795b0a Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Wed, 30 Sep 2020 11:01:31 +0300 Subject: [PATCH 22/28] Add hot fix for submission payload validation (#822) (#825) * Add hot fix for submission payload validation (#822) * Fixed code smell --- .../validation/ValidSubmissionPayload.java | 20 +--------------- .../controller/PayloadValidationTest.java | 24 ++++--------------- 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java index 858c6ef601..7de7d633f1 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java @@ -30,7 +30,6 @@ import java.lang.annotation.Target; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @@ -89,8 +88,7 @@ public boolean isValid(SubmissionPayload submissionPayload, ConstraintValidatorC validatorContext.disableDefaultConstraintViolation(); if (keysHaveFlexibleRollingPeriod(exposureKeys)) { - return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext) - && checkKeysCumulateEqualOrLessThanMaxRollingPeriodPerDay(exposureKeys, validatorContext); + return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext); } else { return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext) && checkKeyCollectionSize(exposureKeys, validatorContext) @@ -128,22 +126,6 @@ private boolean checkUniqueStartIntervalNumbers(List expos return true; } - private boolean checkKeysCumulateEqualOrLessThanMaxRollingPeriodPerDay(List exposureKeys, - ConstraintValidatorContext validatorContext) { - - boolean isValidRollingPeriod = exposureKeys.stream().collect(Collectors - .groupingBy(TemporaryExposureKey::getRollingStartIntervalNumber, - Collectors.summingInt(TemporaryExposureKey::getRollingPeriod))) - .values().stream() - .anyMatch(sum -> sum <= maxRollingPeriod); - - if (!isValidRollingPeriod) { - addViolation(validatorContext, "The sum of the rolling periods exceeds 144 per day"); - return false; - } - return true; - } - private boolean keysHaveFlexibleRollingPeriod(List exposureKeys) { return exposureKeys.stream() .anyMatch(temporaryExposureKey -> temporaryExposureKey.getRollingPeriod() < maxRollingPeriod); diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java index c7dfddb411..aa5a2818c2 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java @@ -125,7 +125,7 @@ void check200ResponseStatusForGapsInTimeIntervalsOfKeysWithFlexibleRollingPeriod @ParameterizedTest @MethodSource("app.coronawarn.server.services.submission.controller.TEKDatasetGeneration#getOverlappingTestDatasets") - void check400ResponseStatusForOverlappingTimeIntervalsI(List dataset) { + void check400ResponseStatusForOverlappingTimeIntervals(List dataset) { ResponseEntity actResponse = executor.executePost(dataset); assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } @@ -163,22 +163,6 @@ private Collection buildPayloadWithMoreThan14KeysAndFlexib return flexibleRollingPeriodKeys; } - @Test - void check400ResponseStatusWhenTwoKeysCumulateMoreThanMaxRollingPeriodInSameDay() { - ResponseEntity actResponse = executor.executePost(buildPayloadWithKeysThatCumulateMoreThanMaxRollingPeriodPerDay()); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); - } - - private Collection buildPayloadWithKeysThatCumulateMoreThanMaxRollingPeriodPerDay() { - ArrayList temporaryExposureKeys = new ArrayList<>(); - temporaryExposureKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, - createRollingStartIntervalNumber(2), 3, 100)); - temporaryExposureKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, - createRollingStartIntervalNumber(2), 3, 144)); - - return temporaryExposureKeys; - } - @Test void check200ResponseStatusWithTwoKeysOneFlexibleAndOneDefaultOnDifferentDays() { ResponseEntity actResponse = executor.executePost(buildPayloadWithTwoKeysOneFlexibleAndOneDefaultOnDifferentDays()); @@ -199,13 +183,13 @@ private Collection buildPayloadWithTwoKeysOneFlexibleAndOn } @Test - void check200ResponseStatusWhenKeysCumulateToMaxRollingPeriodInSameDay() { - ResponseEntity actResponse = executor.executePost(buildPayloadWithTwoKeysWithFlexibleRollingPeriod()); + void check200ResponseStatusWhenReceivingMultipleKeysForTheSameDay() { + ResponseEntity actResponse = executor.executePost(buildPayloadWithKeysWithFlexibleRollingPeriod()); assertThat(actResponse.getStatusCode()).isEqualTo(OK); } - private Collection buildPayloadWithTwoKeysWithFlexibleRollingPeriod() { + private Collection buildPayloadWithKeysWithFlexibleRollingPeriod() { ArrayList flexibleRollingPeriodKeys = new ArrayList<>(); flexibleRollingPeriodKeys.add(buildTemporaryExposureKeyWithFlexibleRollingPeriod(VALID_KEY_DATA_1, From 15331d979e9471ff6e51f839292870860d1f6943 Mon Sep 17 00:00:00 2001 From: ioangut Date: Wed, 30 Sep 2020 14:34:46 +0300 Subject: [PATCH 23/28] fix branch after merge conflicts --- .../ProdDiagnosisKeyBundler.java | 1 + ...odDiagnosisKeyBundlerExpiryPolicyTest.java | 43 ------------------ .../services/distribution/common/Helpers.java | 12 ----- .../runner/TestDataGenerationTest.java | 1 + .../controller/RequestExecutor.java | 7 +++ .../controller/SubmissionControllerTest.java | 44 ++++++++++++------- 6 files changed, 37 insertions(+), 71 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java index d949d16702..b5b9adb88e 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java @@ -20,6 +20,7 @@ package app.coronawarn.server.services.distribution.assembly.diagnosiskeys; +import static java.time.ZoneOffset.UTC; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.groupingBy; diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java index bb37a65fc7..710d33bf63 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java @@ -129,47 +129,4 @@ void testLastPeriodOfHourAndSubmissionGreaterDistributionDateTimeWithFlexibleRol bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0),"DE")).hasSize(10); } - - @ParameterizedTest - @ValueSource(longs = {0L, 24L, 24L + 2L}) - void testLastPeriodOfHourAndSubmissionLessThanDistributionDateTimeWithFlexibleRollingPeriod( - long submissionTimestamp) { - List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, submissionTimestamp, 5, 44); - diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, submissionTimestamp, 5, 100)); - bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); - assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10); - } - - @Test - void testLastPeriodOfHourAndSubmissionEqualsDistributionDateTimeWithFlexibleRollingPeriod() { - List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 3L, 5, 44); - diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 3L, 5, 100)); - bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); - assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10); - } - - @ParameterizedTest - @ValueSource(longs = {0L, 24L, 24L + 2L, 24L + 3L}) - void testFirstPeriodOfHourAndSubmissionLessThanDistributionDateTimeWithFlexibleRollingPeriod(long submissionTimestamp) { - List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, submissionTimestamp, 5, 44); - diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, submissionTimestamp, 5, 100)); - bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); - assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); - } - - @Test - void testFirstPeriodOfHourAndSubmissionEqualsDistributionDateTimeWithFlexibleRollingPeriod() { - List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, 24L + 4L, 5, 44); - diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(6, 24L + 4L, 5, 100)); - bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); - assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); - } - - @Test - void testLastPeriodOfHourAndSubmissionGreaterDistributionDateTimeWithFlexibleRollingPeriod() { - List diagnosisKeys = Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 4L, 5, 44); - diagnosisKeys.addAll(Helpers.buildDiagnosisKeysWithFlexibleRollingPeriod(5, 24L + 4L, 5, 80)); - bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0)); - assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10); - } } diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java index 11eb815ab0..72ef4f5a7c 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java @@ -123,18 +123,6 @@ public static List buildDiagnosisKeysWithFlexibleRollingPeriod( .collect(Collectors.toList()); } - public static List buildDiagnosisKeysWithFlexibleRollingPeriod( - int startIntervalNumber, long submissionTimestamp, int number, int rollingPeriod) { - return IntStream.range(0, number) - .mapToObj(ignoredValue -> DiagnosisKey.builder() - .withKeyData(new byte[16]) - .withRollingStartIntervalNumber(startIntervalNumber) - .withTransmissionRiskLevel(2) - .withSubmissionTimestamp(submissionTimestamp) - .withRollingPeriod(rollingPeriod).build()) - .collect(Collectors.toList()); - } - public static Set getFilePaths(java.io.File root, String basePath) { Set files = Arrays.stream(Objects.requireNonNull(root.listFiles())) .filter(File::isFile) diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java index 5c492e9546..129e62ff25 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/TestDataGenerationTest.java @@ -43,6 +43,7 @@ import java.util.List; import java.util.Set; import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java index 98b7a1ca69..09827aae0f 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java @@ -23,8 +23,12 @@ import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import java.net.URI; +import java.time.Instant; +import java.time.LocalDate; import java.util.Collection; +import java.util.Collections; import java.util.List; +import com.google.protobuf.ByteString; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -32,6 +36,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.VALID_KEY_DATA_1; +import static java.time.ZoneOffset.UTC; + /** * RequestExecutor executes requests against the diagnosis key submission endpoint and holds a various methods for test * request generation. diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java index a8f51680f9..9932081620 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java @@ -20,18 +20,8 @@ package app.coronawarn.server.services.submission.controller; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.VALID_KEY_DATA_2; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeys; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeysWithoutDSOS; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeysWithoutDSOSAndTRL; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeysWithoutTRL; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayload; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithInvalidOriginCountry; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithOneKey; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithPadding; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithTooLargePadding; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithVisitedCountries; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.createRollingStartIntervalNumber; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.*; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithInvalidKey; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -50,6 +40,7 @@ import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.persistence.service.DiagnosisKeyService; +import app.coronawarn.server.common.protocols.external.exposurenotification.ReportType; import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; @@ -123,10 +114,9 @@ void checkResponseStatusForValidParametersWithPadding() { assertThat(actResponse.getStatusCode()).isEqualTo(OK); } - @ParameterizedTest - @MethodSource({"buildPayloadWithInvalidKeys"}) - void check400ResponseStatusForInvalidKeys(SubmissionPayload invalidPayload) { - ResponseEntity actResponse = executor.executePost(invalidPayload); + @Test + void check400ResponseStatusForInvalidKeys() { + ResponseEntity actResponse = executor.executePost(buildPayloadWithInvalidKey()); assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); } @@ -444,4 +434,26 @@ private DiagnosisKey findDiagnosisKeyMatch(TemporaryExposureKey temporaryExposur diagnosisKey -> temporaryExposureKey.getKeyData().equals(ByteString.copyFrom(diagnosisKey.getKeyData()))) .findFirst().orElseThrow(); } + + public static SubmissionPayload buildPayloadWithInvalidKey() { + TemporaryExposureKey invalidKey = + buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2), 999, + ReportType.CONFIRMED_CLINICAL_DIAGNOSIS, 1); + return buildPayload(invalidKey); + } + + private static Stream createIncompleteHeaders() { + return Stream.of( + Arguments.of(HttpHeaderBuilder.builder().build()), + Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().build()), + Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().withoutCwaFake().build()), + Arguments.of(HttpHeaderBuilder.builder().contentTypeProtoBuf().cwaAuth().build())); + } + + private static Stream createDeniedHttpMethods() { + return Arrays.stream(HttpMethod.values()) + .filter(method -> method != HttpMethod.POST) + .filter(method -> method != HttpMethod.PATCH) /* not supported by Rest Template */ + .map(Arguments::of); + } } From c0016492005fdc1fe0bf50c59a05d6d08ef2a7ca Mon Sep 17 00:00:00 2001 From: ioangut Date: Wed, 30 Sep 2020 15:21:48 +0300 Subject: [PATCH 24/28] Adjustments after code review --- services/submission/pom.xml | 6 ------ .../controller/SubmissionControllerTest.java | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/services/submission/pom.xml b/services/submission/pom.xml index 3862ab1aa4..232f4fd949 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -67,12 +67,6 @@ com.google.protobuf protobuf-java-util - - com.google.protobuf - protobuf-java-util - 3.12.4 - test - diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java index 9932081620..a9bf9afe76 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionControllerTest.java @@ -20,8 +20,20 @@ package app.coronawarn.server.services.submission.controller; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.*; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithInvalidKey; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.VALID_KEY_DATA_1; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.VALID_KEY_DATA_2; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayload; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeys; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeysWithoutDSOS; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeysWithoutDSOSAndTRL; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeysWithoutTRL; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithOneKey; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithPadding; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithVisitedCountries; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithTooLargePadding; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.createRollingStartIntervalNumber; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayloadWithInvalidOriginCountry; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildTemporaryExposureKey; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; From aadfe0944d9fe85e10aec090a58719bec7b226a9 Mon Sep 17 00:00:00 2001 From: ioangut Date: Wed, 30 Sep 2020 15:44:08 +0300 Subject: [PATCH 25/28] removed unused getExpiryDateTime method --- .../diagnosiskeys/ProdDiagnosisKeyBundler.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java index b5b9adb88e..dc364c938c 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java @@ -152,16 +152,6 @@ private static Optional getEarliestDistributableTimestamp( return distributableDiagnosisKeys.keySet().stream().min(LocalDateTime::compareTo); } - /** - * Returns the end of the rolling time window that a {@link DiagnosisKey} was active for as a {@link LocalDateTime}. - * The ".plusDays(1L)" is used as there can be now diagnosis keys with rollingPeriod set to less than 1 day. - */ - private LocalDateTime getExpiryDateTime(DiagnosisKey diagnosisKey) { - return LocalDateTime - .ofEpochSecond(diagnosisKey.getRollingStartIntervalNumber() * TEN_MINUTES_INTERVAL_SECONDS, 0, UTC) - .plusDays(1L); - } - /** * Calculates the earliest point in time at which the specified {@link DiagnosisKey} can be distributed, while * respecting the expiry policy and the submission timestamp. Before keys are allowed to be distributed, they must be From 35ca15d28ec5fc81e52e849d3d9372870bc9ed2b Mon Sep 17 00:00:00 2001 From: ioangut Date: Thu, 1 Oct 2020 10:20:57 +0300 Subject: [PATCH 26/28] removed unused import code smell --- .../assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java index dc364c938c..27b55a4743 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java @@ -20,7 +20,6 @@ package app.coronawarn.server.services.distribution.assembly.diagnosiskeys; -import static java.time.ZoneOffset.UTC; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.groupingBy; From a4fb4b300e5a094665cc6ccbfff752feaedf6ed7 Mon Sep 17 00:00:00 2001 From: ioangut <67064882+ioangut@users.noreply.github.com> Date: Thu, 1 Oct 2020 10:44:09 +0300 Subject: [PATCH 27/28] Adjust submission payload validation checks in Master(#832) f (#834) * Adjust submission payload validation checks (#832) * remove unused import code smell --- .../validation/ValidSubmissionPayload.java | 20 +------------------ .../controller/PayloadValidationTest.java | 4 ++-- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java index 7de7d633f1..c4422abb50 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionPayload.java @@ -28,7 +28,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Arrays; import java.util.List; import javax.validation.Constraint; import javax.validation.ConstraintValidator; @@ -91,8 +90,7 @@ public boolean isValid(SubmissionPayload submissionPayload, ConstraintValidatorC return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext); } else { return checkStartIntervalNumberIsAtMidNight(exposureKeys, validatorContext) - && checkKeyCollectionSize(exposureKeys, validatorContext) - && checkUniqueStartIntervalNumbers(exposureKeys, validatorContext); + && checkKeyCollectionSize(exposureKeys, validatorContext); } } @@ -110,22 +108,6 @@ private boolean checkKeyCollectionSize(List exposureKeys, return true; } - private boolean checkUniqueStartIntervalNumbers(List exposureKeys, - ConstraintValidatorContext validatorContext) { - Integer[] startIntervalNumbers = exposureKeys.stream() - .mapToInt(TemporaryExposureKey::getRollingStartIntervalNumber).boxed().toArray(Integer[]::new); - long distinctSize = Arrays.stream(startIntervalNumbers) - .distinct() - .count(); - - if (distinctSize < exposureKeys.size()) { - addViolation(validatorContext, String.format( - "Duplicate StartIntervalNumber found. StartIntervalNumbers: %s", startIntervalNumbers)); - return false; - } - return true; - } - private boolean keysHaveFlexibleRollingPeriod(List exposureKeys) { return exposureKeys.stream() .anyMatch(temporaryExposureKey -> temporaryExposureKey.getRollingPeriod() < maxRollingPeriod); diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java index aa5a2818c2..79fd5a1e37 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/PayloadValidationTest.java @@ -81,7 +81,7 @@ private Collection buildPayloadWithTooManyKeys() { } @Test - void check400ResponseStatusForKeysWithFixedRollingPeriodAndDuplicateStartIntervals() { + void check200ResponseStatusForKeysWithFixedRollingPeriodAndDuplicateStartIntervals() { int rollingStartIntervalNumber = createRollingStartIntervalNumber(2); var keysWithDuplicateStartIntervalNumber = Lists.list( buildTemporaryExposureKey(VALID_KEY_DATA_1, rollingStartIntervalNumber, 1), @@ -89,7 +89,7 @@ void check400ResponseStatusForKeysWithFixedRollingPeriodAndDuplicateStartInterva ResponseEntity actResponse = executor.executePost(keysWithDuplicateStartIntervalNumber); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + assertThat(actResponse.getStatusCode()).isEqualTo(OK); } @Test From 2492186059ad84f3e502e332eddaa559f1564e5f Mon Sep 17 00:00:00 2001 From: ioangut Date: Thu, 1 Oct 2020 11:53:09 +0300 Subject: [PATCH 28/28] Removed unused duplicated methods --- .../controller/RequestExecutor.java | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java index 09827aae0f..c004462ab7 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/RequestExecutor.java @@ -23,12 +23,8 @@ import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import java.net.URI; -import java.time.Instant; -import java.time.LocalDate; import java.util.Collection; -import java.util.Collections; import java.util.List; -import com.google.protobuf.ByteString; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -36,9 +32,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.VALID_KEY_DATA_1; -import static java.time.ZoneOffset.UTC; - /** * RequestExecutor executes requests against the diagnosis key submission endpoint and holds a various methods for test * request generation. @@ -85,32 +78,4 @@ private HttpHeaders buildDefaultHeader() { .withoutCwaFake() .build(); } - - public static TemporaryExposureKey buildTemporaryExposureKey( - String keyData, int rollingStartIntervalNumber, int transmissionRiskLevel) { - return TemporaryExposureKey.newBuilder() - .setKeyData(ByteString.copyFromUtf8(keyData)) - .setRollingStartIntervalNumber(rollingStartIntervalNumber) - .setTransmissionRiskLevel(transmissionRiskLevel).build(); - } - - public static TemporaryExposureKey buildTemporaryExposureKeyWithFlexibleRollingPeriod( - String keyData, int rollingStartIntervalNumber, int transmissionRiskLevel, int rollingPeriod) { - return TemporaryExposureKey.newBuilder() - .setKeyData(ByteString.copyFromUtf8(keyData)) - .setRollingStartIntervalNumber(rollingStartIntervalNumber) - .setTransmissionRiskLevel(transmissionRiskLevel) - .setRollingPeriod(rollingPeriod).build(); - } - - public static int createRollingStartIntervalNumber(Integer daysAgo) { - return Math.toIntExact(LocalDate - .ofInstant(Instant.now(), UTC) - .minusDays(daysAgo).atStartOfDay() - .toEpochSecond(UTC) / (60 * 10)); - } - - public static Collection buildPayloadWithOneKey() { - return Collections.singleton(buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(1), 3)); - } }