From a1e87b69ce7594a37bec7d220b03ec728de7c958 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 1 Nov 2022 13:59:48 +0100 Subject: [PATCH 01/83] cwa-protocol-buffers/pull/57 --- .../internal/submission_payload.proto | 6 ++++++ .../internal/v2/app_config_android.proto | 3 +++ .../internal/v2/app_config_ios.proto | 3 +++ .../internal/v2/ppdd_srs_parameters.proto | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/ppdd_srs_parameters.proto diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/submission_payload.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/submission_payload.proto index 48c5623c48..309d803274 100644 --- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/submission_payload.proto +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/submission_payload.proto @@ -19,5 +19,11 @@ message SubmissionPayload { SUBMISSION_TYPE_PCR_TEST = 0; SUBMISSION_TYPE_RAPID_TEST = 1; SUBMISSION_TYPE_HOST_WARNING = 2; + SUBMISSION_TYPE_SRS_SELF_TEST = 3; + SUBMISSION_TYPE_SRS_RAT = 4; + SUBMISSION_TYPE_SRS_REGISTERED_PCR = 5; + SUBMISSION_TYPE_SRS_UNREGISTERED_PCR = 6; + SUBMISSION_TYPE_SRS_RAPID_PCR = 7; + SUBMISSION_TYPE_SRS_OTHER = 8; } } diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_android.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_android.proto index 1fee4433a5..b8f3434698 100644 --- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_android.proto +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_android.proto @@ -10,6 +10,7 @@ import "app/coronawarn/server/common/protocols/internal/v2/key_download_paramete import "app/coronawarn/server/common/protocols/internal/v2/ppdd_edus_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/ppdd_els_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/ppdd_ppa_parameters.proto"; +import "app/coronawarn/server/common/protocols/internal/v2/ppdd_srs_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/presence_tracing_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/risk_calculation_parameters.proto"; @@ -41,6 +42,8 @@ message ApplicationConfigurationAndroid { CoronaTestParameters coronaTestParameters = 14; DGCParameters dgcParameters = 15; + + PPDDSelfReportSubmissionParametersAndroid selfReportParameters = 16; } message DiagnosisKeysDataMapping { diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_ios.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_ios.proto index 1727cebb14..8a81bca12a 100644 --- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_ios.proto +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/app_config_ios.proto @@ -10,6 +10,7 @@ import "app/coronawarn/server/common/protocols/internal/v2/key_download_paramete import "app/coronawarn/server/common/protocols/internal/v2/ppdd_edus_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/ppdd_els_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/ppdd_ppa_parameters.proto"; +import "app/coronawarn/server/common/protocols/internal/v2/ppdd_srs_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/risk_calculation_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/presence_tracing_parameters.proto"; import "app/coronawarn/server/common/protocols/internal/v2/semantic_version.proto"; @@ -40,6 +41,8 @@ message ApplicationConfigurationIOS { CoronaTestParameters coronaTestParameters = 13; DGCParameters dgcParameters = 14; + + PPDDSelfReportSubmissionParametersIOS selfReportParameters = 15; } message ExposureConfiguration { diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/ppdd_srs_parameters.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/ppdd_srs_parameters.proto new file mode 100644 index 0000000000..d0f9910bc0 --- /dev/null +++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/v2/ppdd_srs_parameters.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package app.coronawarn.server.common.protocols.internal.v2; +option java_multiple_files = true; +import "app/coronawarn/server/common/protocols/internal/v2/ppdd_ppac_parameters.proto"; + +message PPDDSelfReportSubmissionParametersIOS { + PPDDSelfReportSubmissionParametersCommon common = 1; + PPDDPrivacyPreservingAccessControlParametersIOS ppac = 2; +} + +message PPDDSelfReportSubmissionParametersAndroid { + PPDDSelfReportSubmissionParametersCommon common = 1; + PPDDPrivacyPreservingAccessControlParametersAndroid ppac = 2; +} + +message PPDDSelfReportSubmissionParametersCommon { + int32 timeSinceOnboardingInHours = 1; + int32 timeBetweenSubmissionsInDays = 2; +} \ No newline at end of file From da82b5b65d328d25d1550710fa60ffe28eba39a2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 3 Nov 2022 14:25:47 +0100 Subject: [PATCH 02/83] allow all kinds of submissionTypes on diagnosis_key except: SUBMISSION_TYPE_HOST_WARNING --- .../ValidSubmissionTypeValidator.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidator.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidator.java index d0f483dd37..e35386d214 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidator.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidator.java @@ -1,14 +1,25 @@ package app.coronawarn.server.common.persistence.domain.validation; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING; + +import app.coronawarn.server.common.persistence.domain.CheckInProtectedReports; +import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; +/** + * Validation applied onto the 'submission_type' column at 'diagnosis_key' table. + */ public class ValidSubmissionTypeValidator implements ConstraintValidator { + /** + * In case of a host-warning (aka. on-behalf-of check-in warning), there won't be any {@link DiagnosisKey}s, but only + * {@link CheckInProtectedReports}. So SUBMISSION_TYPE_HOST_WARNING isn't allowed to be stored in diagnosis_key table. + */ @Override - public boolean isValid(SubmissionType submissionType, ConstraintValidatorContext constraintValidatorContext) { - return SubmissionType.SUBMISSION_TYPE_PCR_TEST.equals(submissionType) - || SubmissionType.SUBMISSION_TYPE_RAPID_TEST.equals(submissionType); + public boolean isValid(final SubmissionType submissionType, + final ConstraintValidatorContext constraintValidatorContext) { + return !SUBMISSION_TYPE_HOST_WARNING.equals(submissionType); } } From dfbd4f0e3f43e2386fbeb1c90b5d91351a587aae Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 3 Nov 2022 14:42:07 +0100 Subject: [PATCH 03/83] new DB table: self_report_submissions --- .../repository/DiagnosisKeyRepository.java | 12 +++++++++ .../service/DiagnosisKeyService.java | 6 +++++ .../db/migration/R__createViewTableStats.sql | 27 +++++++++++++++++++ .../db/migration/V24__createSRSTables.sql | 12 +++++++++ .../DiagnosisKeyRepositoryTest.java | 5 ++++ 5 files changed, 62 insertions(+) create mode 100644 common/persistence/src/main/resources/db/migration/R__createViewTableStats.sql create mode 100644 common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql 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 6f0a4c7e59..add740208b 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 @@ -90,4 +90,16 @@ boolean saveDoNothingOnConflict( + " ORDER BY submission_timestamp") List findAllWithTrlGreaterThanOrEqual(final @Param("minTrl") int minTrl, @Param("threshold") long submissionTimestamp); + + /** + * For each Self-Reported-Submission, we'll create one record to get a glimpse on what's going on. + * + * @param submissionType - depending on what the client has chosen + * @return true, when insert into self_report_submissions was successful + */ + @Modifying + @Query("INSERT INTO self_report_submissions " + + "(submission_type) " + + "VALUES (:submission_type) ") + boolean recordSrs(final @Param("submission_type") String submissionType); } diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java index 67e7f30bb1..10132f4972 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java @@ -33,6 +33,12 @@ public DiagnosisKeyService(DiagnosisKeyRepository keyRepository, ValidDiagnosisK this.validationFilter = filter; } + @Timed + @Transactional + public boolean recordSrs(final SubmissionType submissionType) { + return keyRepository.recordSrs(submissionType.name()); + } + /** * Persists the specified collection of {@link DiagnosisKey} instances and returns the number of inserted diagnosis * keys. If the key data of a particular diagnosis key already exists in the database and is of a submission type that diff --git a/common/persistence/src/main/resources/db/migration/R__createViewTableStats.sql b/common/persistence/src/main/resources/db/migration/R__createViewTableStats.sql new file mode 100644 index 0000000000..a255c9259d --- /dev/null +++ b/common/persistence/src/main/resources/db/migration/R__createViewTableStats.sql @@ -0,0 +1,27 @@ +DROP VIEW IF EXISTS "submission_stats"; + +CREATE OR REPLACE VIEW submission_stats AS + SELECT COUNT(*), TO_TIMESTAMP(submission_timestamp * 3600)::DATE AS submission_date, 'diagnosis_key' AS tab + FROM diagnosis_key + GROUP BY submission_date + UNION + SELECT COUNT(*), TO_TIMESTAMP(submission_timestamp * 3600)::DATE AS submission_date, 'check_in_protected_reports' + FROM check_in_protected_reports + GROUP BY submission_date + UNION + SELECT COUNT(*), TO_TIMESTAMP(submission_timestamp * 3600)::DATE AS submission_date, 'trace_time_interval_warning' + FROM trace_time_interval_warning + GROUP BY submission_date + UNION + SELECT COUNT(*), TO_TIMESTAMP(submission_timestamp * 3600)::DATE AS submission_date, 'federation_upload_key' + FROM federation_upload_key + GROUP BY submission_date + UNION + SELECT COUNT(*), TO_TIMESTAMP(submission_timestamp * 3600)::DATE AS submission_date, 'chgs_upload_key' + FROM chgs_upload_key + GROUP BY submission_date + UNION + SELECT COUNT(*), submission_date, 'self_report_submissions' + FROM self_report_submissions + GROUP BY submission_date +; diff --git a/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql b/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql new file mode 100644 index 0000000000..8fd8c6c27a --- /dev/null +++ b/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS self_report_submissions ( + submission_type VARCHAR(30) NOT NULL, + submission_date DATE NOT NULL DEFAULT CURRENT_DATE +); + +GRANT INSERT ON TABLE self_report_submissions TO "cwa_submission"; + +CREATE OR REPLACE VIEW self_reports AS + SELECT COUNT(*), submission_date, submission_type + FROM self_report_submissions + GROUP BY submission_date, submission_type +; diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java index b516b50564..65bc33f828 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java @@ -52,4 +52,9 @@ void shouldCheckExistence() { assertTrue(repository.exists(id, type.name())); } + + @Test + void recordSRSTest() { + assertTrue(repository.recordSrs(SubmissionType.SUBMISSION_TYPE_SRS_RAPID_PCR.name())); + } } From 840193132adeeb384ac5621adffb2e3e91a11bfc Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 3 Nov 2022 14:44:09 +0100 Subject: [PATCH 04/83] TODO: implement SrsOtpVerifier --- .../verification/SrsOtpVerifier.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java new file mode 100644 index 0000000000..2169a89b5a --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java @@ -0,0 +1,35 @@ +package app.coronawarn.server.services.submission.verification; + +import feign.FeignException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * The SRS OTP verifier performs the verification of SRS OTPs. + */ +@Service +public class SrsOtpVerifier extends TanVerificationService { + + private static final Logger logger = LoggerFactory.getLogger(SrsOtpVerifier.class); + + public SrsOtpVerifier(VerificationServerClient verificationServerClient) { + super(verificationServerClient); + } + + boolean verifyWithVerificationService(Tan tan) { + try { + logger.info("Calling SRS OPT verification Service ..."); + + // FIXME! + // ResponseEntity result = verificationServerClient.verifyTan(tan); + // List typeHeaders = result.getHeaders().getOrEmpty(CWA_TELETAN_TYPE_RESPONSE_HEADER); + // logger.info("Received response from Verification Service: {}", result); + + return true; + } catch (FeignException.NotFound e) { + logger.info("SRS OTP verification service reported: NotFound", e); + return false; + } + } +} From 8b03e1d9dad65f9ea4bac2dfb224ae7b20979ea2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 3 Nov 2022 18:27:31 +0100 Subject: [PATCH 05/83] introduce: "cwa-otp" header either-or: cwa-otp | cwa-authorization --- .../controller/SubmissionController.java | 82 ++++++++++- .../integration/SubmissionPersistenceIT.java | 131 ++++++++++++++++-- 2 files changed, 197 insertions(+), 16 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 12b9bd0eb7..70be73a04b 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -1,11 +1,17 @@ package app.coronawarn.server.services.submission.controller; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING_VALUE; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_OTHER_VALUE; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST_VALUE; +import static org.springframework.util.ObjectUtils.isEmpty; + import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.persistence.domain.config.TrlDerivations; import app.coronawarn.server.common.persistence.domain.validation.ValidRollingStartIntervalNumberValidator; 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.common.protocols.internal.SubmissionPayload.SubmissionType; import app.coronawarn.server.common.shared.util.HashUtils; import app.coronawarn.server.services.submission.checkins.EventCheckinFacade; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; @@ -15,6 +21,7 @@ import app.coronawarn.server.services.submission.validation.ValidSubmissionOnBehalfPayload; import app.coronawarn.server.services.submission.validation.ValidSubmissionPayload; import app.coronawarn.server.services.submission.verification.EventTanVerifier; +import app.coronawarn.server.services.submission.verification.SrsOtpVerifier; import app.coronawarn.server.services.submission.verification.TanVerificationService; import app.coronawarn.server.services.submission.verification.TanVerifier; import feign.FeignException; @@ -56,6 +63,7 @@ public class SubmissionController { private final DiagnosisKeyService diagnosisKeyService; private final TanVerifier tanVerifier; private final EventTanVerifier eventTanVerifier; + private final SrsOtpVerifier srsOtpVerifier; private final Integer retentionDays; private final Integer randomKeyPaddingMultiplier; private final FakeDelayManager fakeDelayManager; @@ -65,12 +73,13 @@ public class SubmissionController { private final ValidRollingStartIntervalNumberValidator rollingStartIntervalNumberValidator; SubmissionController(DiagnosisKeyService diagnosisKeyService, TanVerifier tanVerifier, - EventTanVerifier eventTanVerifier, FakeDelayManager fakeDelayManager, + EventTanVerifier eventTanVerifier, final SrsOtpVerifier srsOtpVerifier, FakeDelayManager fakeDelayManager, SubmissionServiceConfig submissionServiceConfig, SubmissionMonitor submissionMonitor, EventCheckinFacade eventCheckinFacade) { this.diagnosisKeyService = diagnosisKeyService; this.tanVerifier = tanVerifier; this.eventTanVerifier = eventTanVerifier; + this.srsOtpVerifier = srsOtpVerifier; this.submissionMonitor = submissionMonitor; this.fakeDelayManager = fakeDelayManager; this.submissionServiceConfig = submissionServiceConfig; @@ -88,16 +97,75 @@ public class SubmissionController { * @param tan A tan for diagnosis verification. * @return An empty response body. */ - @PostMapping(value = SUBMISSION_ROUTE, headers = {"cwa-fake=0"}) + @PostMapping(value = SUBMISSION_ROUTE, headers = { "cwa-fake=0" }) @Timed(description = "Time spent handling submission.") public DeferredResult> submitDiagnosisKey( @ValidSubmissionPayload @RequestBody SubmissionPayload exposureKeys, - @RequestHeader("cwa-authorization") String tan) { + @RequestHeader(name = "cwa-authorization", required = false) String tan, + @RequestHeader(name = "cwa-otp", required = false) String otp) { + + if (isEmpty(tan) && isEmpty(otp)) { + logger.warn("'cwa-authorization' and 'cwa-otp' header missing!"); + return badRequest(); + } + if (!isEmpty(tan) && !isEmpty(otp)) { + logger.warn("'cwa-authorization' and 'cwa-otp' header provided!"); + return badRequest(); + } + + if (!isEmpty(otp)) { + if (!validSrsType(exposureKeys.getSubmissionType().getNumber())) { + return badRequest(); + } + logger.debug("SRS: {} - {}", otp, exposureKeys); + return buildRealDeferredResult(exposureKeys, otp, srsOtpVerifier); + } + + if (!validSubmissionType(exposureKeys.getSubmissionType().getNumber())) { + return badRequest(); + } + submissionMonitor.incrementRequestCounter(); submissionMonitor.incrementRealRequestCounter(); return buildRealDeferredResult(exposureKeys, tan, tanVerifier); } + /** + * Valid regular submission types: SUBMISSION_TYPE_PCR_TEST_VALUE, SUBMISSION_TYPE_RAPID_TEST_VALUE, + * SUBMISSION_TYPE_HOST_WARNING_VALUE. + * + * @param submissionTypeValue int value of the enum + * @return true if it's ok. + * + * @see SubmissionType + */ + private boolean validSubmissionType(int submissionTypeValue) { + return 0 <= submissionTypeValue && submissionTypeValue <= SUBMISSION_TYPE_HOST_WARNING_VALUE; + } + + /** + * Valid SRS types: SUBMISSION_TYPE_SRS_SELF_TEST, SUBMISSION_TYPE_SRS_RAT, SUBMISSION_TYPE_SRS_REGISTERED_PCR, + * SUBMISSION_TYPE_SRS_UNREGISTERED_PCR, SUBMISSION_TYPE_SRS_RAPID_PCR, SUBMISSION_TYPE_SRS_OTHER. + * + * @param submissionTypeValue int value of the enum + * @return true if it's ok. + * + * @see SubmissionType + */ + private boolean validSrsType(int submissionTypeValue) { + return SUBMISSION_TYPE_SRS_SELF_TEST_VALUE <= submissionTypeValue + && submissionTypeValue <= SUBMISSION_TYPE_SRS_OTHER_VALUE; + } + + /** + * {@link ResponseEntity#badRequest()} wrapped into {@link DeferredResult}. + * + * @return {@link HttpStatus#BAD_REQUEST} + */ + private DeferredResult> badRequest() { + return new DeferredResult<>(null, () -> ResponseEntity.badRequest().build()); + } + /** * Handles "submission on behalf" requests. The basic idea is, that public health departments should be enabled to * warn all participants of a certain event although the department didn't join the event - it's like: "warn on behalf @@ -107,7 +175,7 @@ public DeferredResult> submitDiagnosisKey( * @param tan A tan for diagnosis verification. * @return An empty response body. */ - @PostMapping(value = SUBMISSION_ON_BEHALF_ROUTE, headers = {"cwa-fake=0"}) + @PostMapping(value = SUBMISSION_ON_BEHALF_ROUTE, headers = { "cwa-fake=0" }) @Timed(description = "Time spent handling submission.") public DeferredResult> submissionOnBehalf( @ValidSubmissionOnBehalfPayload @RequestBody SubmissionPayload submissionPayload, @@ -139,6 +207,10 @@ private DeferredResult> buildRealDeferredResult(SubmissionP extractAndStoreDiagnosisKeys(submissionPayload); CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(submissionPayload); + if (validSrsType(submissionPayload.getSubmissionType().getNumber())) { + diagnosisKeyService.recordSrs(submissionPayload.getSubmissionType()); + } + deferredResult.setResult(ResponseEntity.ok() .header(CWA_FILTERED_CHECKINS_HEADER, String.valueOf(checkinsStorageResult.getNumberOfFilteredCheckins())) .header(CWA_SAVED_CHECKINS_HEADER, String.valueOf(checkinsStorageResult.getNumberOfSavedCheckins())) @@ -194,7 +266,7 @@ private List extractValidDiagnosisKeysFromPayload(SubmissionPayloa if (protoBufferKeys.size() > diagnosisKeys.size()) { logger.warn("Not persisting {} one or more diagnosis key(s), as it is outdated beyond retention threshold or the " - + "RollingStartIntervalNumber is in the future. Payload: {}", + + "RollingStartIntervalNumber is in the future. Payload: {}", protoBufferKeys.size() - diagnosisKeys.size(), new PrintableSubmissionPayload(submissionPayload)); } 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 6dcb0227cc..ee78963c0c 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 @@ -1,6 +1,11 @@ package app.coronawarn.server.services.submission.integration; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_PCR_TEST; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_RAPID_PCR; import static app.coronawarn.server.services.submission.assertions.SubmissionAssertions.assertElementsCorrespondToEachOther; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SUBMISSION_ON_BEHALF_ROUTE; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SUBMISSION_ROUTE; import static app.coronawarn.server.services.submission.integration.DataHelpers.buildDefaultCheckIn; import static app.coronawarn.server.services.submission.integration.DataHelpers.buildDefaultEncryptedCheckIn; import static app.coronawarn.server.services.submission.integration.DataHelpers.buildSubmissionPayload; @@ -23,6 +28,8 @@ 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.EventTanVerifier; +import app.coronawarn.server.services.submission.verification.SrsOtpVerifier; import app.coronawarn.server.services.submission.verification.TanVerifier; import com.google.common.io.BaseEncoding; import com.google.protobuf.util.JsonFormat; @@ -37,6 +44,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Random; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; @@ -54,6 +62,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.core.JdbcTemplate; @@ -65,8 +74,7 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext @ActiveProfiles("integration-test") -@Sql(scripts = {"classpath:db/clean_db_state.sql"}, - executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(scripts = { "classpath:db/clean_db_state.sql" }, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) class SubmissionPersistenceIT { private static final Logger logger = LoggerFactory.getLogger(SubmissionPersistenceIT.class); @@ -93,12 +101,20 @@ class SubmissionPersistenceIT { @MockBean private TanVerifier tanVerifier; + @MockBean + private SrsOtpVerifier srsOtpVerifier; + + @MockBean + private EventTanVerifier eventTanVerifier; + @MockBean private FakeDelayManager fakeDelayManager; @BeforeEach public void setUpMocks() { when(tanVerifier.verifyTan(anyString())).thenReturn(true); + when(srsOtpVerifier.verifyTan(anyString())).thenReturn(true); + when(eventTanVerifier.verifyTan(anyString())).thenReturn(true); when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); } @@ -111,31 +127,125 @@ void testDiagnosisKeyRollingPeriodIsZeroShouldNotBeSaved() { List validKeys = createValidTemporaryExposureKeys(1); SubmissionPayload invalidSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - invalidKeys, SubmissionType.SUBMISSION_TYPE_PCR_TEST); + invalidKeys, SUBMISSION_TYPE_PCR_TEST); String tan = "tan"; - HttpHeaders headers = new HttpHeaders(); - headers.add("cwa-fake", "0"); + final HttpHeaders headers = headers(); headers.add("cwa-authorization", tan); - headers.setContentType(MediaType.valueOf("Application/x-protobuf")); SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - validKeys, SubmissionType.SUBMISSION_TYPE_PCR_TEST); + validKeys, SUBMISSION_TYPE_PCR_TEST); testRestTemplate - .postForEntity("/version/v1/diagnosis-keys", new HttpEntity<>(invalidSubmissionPayload, headers), Void.class); + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(invalidSubmissionPayload, headers), + Void.class); assertEquals(0, diagnosisKeyRepository.count()); testRestTemplate - .postForEntity("/version/v1/diagnosis-keys", new HttpEntity<>(validSubmissionPayload, headers), Void.class); + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), Void.class); final int expectedNumberOfKeys = validSubmissionPayload.getKeysList().size() * config.getRandomKeyPaddingMultiplier(); assertEquals(expectedNumberOfKeys, diagnosisKeyRepository.count()); } + /** + * Test when (cwa-authorization && cwa-otp) are missing. + */ + @Test + void testBadRequest0() { + final List validKeys = createValidTemporaryExposureKeys(1); + final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, + validKeys, SUBMISSION_TYPE_SRS_RAPID_PCR); + + final ResponseEntity response = testRestTemplate + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers()), + Void.class); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + /** + * Test when (cwa-authorization && cwa-otp) are both provided. + */ + @Test + void testBadRequest2() { + final List validKeys = createValidTemporaryExposureKeys(1); + final HttpHeaders headers = headers(); + + headers.add("cwa-authorization", "foo"); + headers.add("cwa-otp", "bar"); + + final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, + validKeys, SUBMISSION_TYPE_SRS_RAPID_PCR); + + final ResponseEntity response = testRestTemplate + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), Void.class); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + private HttpHeaders headers() { + final HttpHeaders headers = new HttpHeaders(); + headers.add("cwa-fake", "0"); + headers.setContentType(MediaType.valueOf("Application/x-protobuf")); + return headers; + } + + /** + * Test Self-Report Submission (SRS). + * + * @param submissionTypeNumber - {@link SubmissionType} + */ + @ParameterizedTest + @ValueSource(ints = { 3, 4, 5, 6, 7, 8 }) // SUBMISSION_TYPE_SRS_* + void testSrsOtpAuth(int submissionTypeNumber) { + final List validKeys = createValidTemporaryExposureKeys(1); + final HttpHeaders headers = headers(); + headers.add("cwa-otp", "bar"); + + final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, + validKeys, SubmissionType.forNumber(submissionTypeNumber)); + + final int before = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM self_report_submissions", Integer.class); + + final ResponseEntity response = testRestTemplate + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), + Void.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + final int result = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM self_report_submissions", Integer.class); + assertEquals(before + 1, result); + } + + @Test + @SuppressWarnings("deprecation") + void testSubmissionOnBehalf() { + byte[] locationIdHash = new byte[32]; + new Random().nextBytes(locationIdHash); + List protectedReports = List.of(DataHelpers.buildDefaultEncryptedCheckIn(locationIdHash), + DataHelpers.buildDefaultEncryptedCheckIn(locationIdHash)); + List checkIns = List.of(DataHelpers.buildDefaultCheckIn(), DataHelpers.buildDefaultCheckIn()); + SubmissionPayload validSubmissionPayload = SubmissionPayload.newBuilder().addAllKeys(Collections.emptyList()) + .addAllVisitedCountries(Collections.emptyList()).setConsentToFederation(false) + .setSubmissionType(SUBMISSION_TYPE_HOST_WARNING).addAllCheckInProtectedReports(protectedReports) + .addAllCheckIns(checkIns).build(); + final HttpHeaders headers = headers(); + headers.add("cwa-authorization", "is mocked and returns true :-)"); + + final int before = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM check_in_protected_reports", Integer.class); + final int none = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM diagnosis_key", Integer.class); + + final ResponseEntity response = testRestTemplate.postForEntity("/version/v1" + SUBMISSION_ON_BEHALF_ROUTE, + new HttpEntity<>(validSubmissionPayload, headers), Void.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + final int result = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM check_in_protected_reports", Integer.class); + assertEquals(before + 2, result); + assertEquals(none, jdbcTemplate.queryForObject("SELECT COUNT(*) FROM diagnosis_key", Integer.class)); + } @ParameterizedTest - @ValueSource(strings = {PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB}) + @ValueSource(strings = { PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB }) void testKeyInsertionWithMobileClientProtoBuf(String testFile) throws IOException { List temporaryExposureKeys = createValidTemporaryExposureKeys(); SubmissionPayload submissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, @@ -391,7 +501,6 @@ void unencryptedCheckInsEnabledShouldResultInSavingCorrectNumberOfCheckIns() { assertThat(result.getHeaders().get("cwa-saved-checkins").get(0)).isEqualTo("3"); } - private String generateDebugSqlStatement(SubmissionPayload payload) { List base64Keys = payload.getKeysList() .stream() From 9d6530e2767b90dd03d33cec5dbd64314048f246 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 7 Nov 2022 10:31:30 +0100 Subject: [PATCH 06/83] let's be OS independent! --- .../integration/TraceTimeIntervalWarningsDistributionIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/tracewarnings/structure/integration/TraceTimeIntervalWarningsDistributionIT.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/tracewarnings/structure/integration/TraceTimeIntervalWarningsDistributionIT.java index c5517dc68f..995342cadd 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/tracewarnings/structure/integration/TraceTimeIntervalWarningsDistributionIT.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/tracewarnings/structure/integration/TraceTimeIntervalWarningsDistributionIT.java @@ -202,7 +202,7 @@ void testIndicesAreOldestAndLatestForMultipleEncryptedSubmissions() throws Excep .filter(path -> hasSuffix(path, latestHour, oldestHour)) .map(this::extractSubmissionHour).collect(Collectors.toList()); final List actualHourlyFilesBetweenOldestAndLatest = actualFiles.stream() - .filter(path -> Pattern.matches("^[a-zA-Z\\/]+\\d+$", path)) + .filter(path -> Pattern.matches("^[a-zA-Z\\" + separator + "]+\\d+$", path)) .map(this::extractSubmissionHour) .filter(submissionTimestamp -> oldestHour <= submissionTimestamp && latestHour >= submissionTimestamp) .collect(Collectors.toList()); From 4dfbf8ad6742dfc6694876c4afee326b4acfe7a7 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 7 Nov 2022 11:16:47 +0100 Subject: [PATCH 07/83] ensure enough space for the submission_type --- .../main/resources/db/migration/V24__createSRSTables.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql b/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql index 8fd8c6c27a..40cd359c33 100644 --- a/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql +++ b/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS self_report_submissions ( - submission_type VARCHAR(30) NOT NULL, + submission_type VARCHAR(40) NOT NULL, submission_date DATE NOT NULL DEFAULT CURRENT_DATE ); @@ -10,3 +10,8 @@ CREATE OR REPLACE VIEW self_reports AS FROM self_report_submissions GROUP BY submission_date, submission_type ; + +ALTER TABLE diagnosis_key ALTER COLUMN submission_type TYPE VARCHAR(40); +ALTER TABLE federation_upload_key ALTER COLUMN submission_type TYPE VARCHAR(40); +ALTER TABLE chgs_upload_key ALTER COLUMN submission_type TYPE VARCHAR(40); +ALTER TABLE trace_time_interval_warning ALTER COLUMN submission_type TYPE VARCHAR(40); From 13785dbb668be8d75b6972b00783c0053ce11270 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 7 Nov 2022 12:35:38 +0100 Subject: [PATCH 08/83] ValidSubmissionTypeValidatorTest --- .../ValidSubmissionTypeValidatorTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidatorTest.java diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidatorTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidatorTest.java new file mode 100644 index 0000000000..f8d998c024 --- /dev/null +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/domain/validation/ValidSubmissionTypeValidatorTest.java @@ -0,0 +1,27 @@ +package app.coronawarn.server.common.persistence.domain.validation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; + +import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class ValidSubmissionTypeValidatorTest { + + @ParameterizedTest + @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_HOST_WARNING" }) + final void isInvalidTest(final SubmissionType type) { + final ValidSubmissionTypeValidator validator = new ValidSubmissionTypeValidator(); + // host warnings, aren't stored in DiagnosisKey table! + assertFalse(validator.isValid(type, null), type + " is valid!?!"); + } + + @ParameterizedTest + @EnumSource(value = SubmissionType.class, mode = EXCLUDE, names = { "SUBMISSION_TYPE_HOST_WARNING" }) + final void isValidTest(final SubmissionType type) { + final ValidSubmissionTypeValidator validator = new ValidSubmissionTypeValidator(); + assertTrue(validator.isValid(type, null), type + " failed!?!"); + } +} From 0f6eaa284f5abd735117082125fcc117597020ec Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 7 Nov 2022 12:49:00 +0100 Subject: [PATCH 09/83] test diagnosisKeyService#recordSrs --- .../persistence/service/DiagnosisKeyServiceTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java index efa7839b84..bdaa3f0b1b 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; @@ -259,4 +260,10 @@ void ignoresRapidTestDiagnosisKeysWhenPcrTestIsPresent() { assertTrue(storedKeys.contains(pcrKey)); assertFalse(storedKeys.contains(rapidKey)); } + + @ParameterizedTest + @EnumSource(value = SubmissionType.class) + void recordSrsTest(final SubmissionType type) { + diagnosisKeyService.recordSrs(type); + } } From 6eef966cc98a536285f303499f0aa01ad5f98f3a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 8 Nov 2022 13:52:30 +0100 Subject: [PATCH 10/83] java17 --- pom.xml | 2 +- services/callback/Dockerfile | 4 ++-- services/distribution/Dockerfile | 4 ++-- services/download/Dockerfile | 4 ++-- services/submission/Dockerfile | 4 ++-- services/upload/Dockerfile | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 6630ca4241..66597e5250 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ UTF-8 - 11 + 17 ${java.version} ${java.version} true diff --git a/services/callback/Dockerfile b/services/callback/Dockerfile index 901fbc3305..c3287f2bbd 100644 --- a/services/callback/Dockerfile +++ b/services/callback/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11 as build +FROM openjdk:17 as build ARG WORK_DIR=/build ARG SERVICE=callback @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java-debian11:11 +FROM gcr.io/distroless/java17-debian11 ARG SERVICE=callback diff --git a/services/distribution/Dockerfile b/services/distribution/Dockerfile index 3bf9f23391..22b3fe9753 100644 --- a/services/distribution/Dockerfile +++ b/services/distribution/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11 as build +FROM openjdk:17 as build ARG WORK_DIR=/build ARG SERVICE=distribution @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java-debian11:11 +FROM gcr.io/distroless/java17-debian11 ARG SERVICE=distribution diff --git a/services/download/Dockerfile b/services/download/Dockerfile index 23abec70fa..e957c391dc 100644 --- a/services/download/Dockerfile +++ b/services/download/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11 as build +FROM openjdk:17 as build ARG WORK_DIR=/build ARG SERVICE=download @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java-debian11:11 +FROM gcr.io/distroless/java17-debian11 ARG SERVICE=download diff --git a/services/submission/Dockerfile b/services/submission/Dockerfile index cd81065f59..d54e7538ec 100644 --- a/services/submission/Dockerfile +++ b/services/submission/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11 as build +FROM openjdk:17 as build ARG WORK_DIR=/build ARG SERVICE=submission @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java-debian11:11 +FROM gcr.io/distroless/java17-debian11 ARG SERVICE=submission diff --git a/services/upload/Dockerfile b/services/upload/Dockerfile index 0bb78597b0..e08df1a3f4 100644 --- a/services/upload/Dockerfile +++ b/services/upload/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11 as build +FROM openjdk:17 as build ARG WORK_DIR=/build ARG SERVICE=upload @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java-debian11:11 +FROM gcr.io/distroless/java17-debian11 ARG SERVICE=upload From aa01812aa45095cb1f5d5a62e98bb2039787a59a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 8 Nov 2022 13:57:40 +0100 Subject: [PATCH 11/83] mitigate Log Injection --- .../services/submission/controller/SubmissionController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 70be73a04b..a38d86234d 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -117,7 +117,6 @@ public DeferredResult> submitDiagnosisKey( if (!validSrsType(exposureKeys.getSubmissionType().getNumber())) { return badRequest(); } - logger.debug("SRS: {} - {}", otp, exposureKeys); return buildRealDeferredResult(exposureKeys, otp, srsOtpVerifier); } From 2dd4afa859754d18b2c72c1d7fe8df71377520f2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 8 Nov 2022 14:17:39 +0100 Subject: [PATCH 12/83] more integration tests - especially for BAD_REQUEST --- .../integration/SubmissionPersistenceIT.java | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) 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 ee78963c0c..3ca4c19b0e 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 @@ -52,6 +52,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; @@ -184,6 +186,36 @@ void testBadRequest2() { assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } + @ParameterizedTest + @EnumSource(value = SubmissionType.class, names = { "(?!SUBMISSION_TYPE_SRS_).*" }, mode = Mode.MATCH_ANY) + void testBadRequest3(final SubmissionType type) { + final List keys = createValidTemporaryExposureKeys(1); + final HttpHeaders headers = headers(); + headers.add("cwa-otp", "bar"); + + final SubmissionPayload payload = buildSubmissionPayload(List.of("DE"), "DE", true, keys, type); + + final ResponseEntity response = testRestTemplate + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(payload, headers), Void.class); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + @ParameterizedTest + @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) + void testBadRequest4(final SubmissionType type) { + final List keys = createValidTemporaryExposureKeys(1); + final HttpHeaders headers = headers(); + headers.add("cwa-authorization", "foo"); + + final SubmissionPayload payload = buildSubmissionPayload(List.of("DE"), "DE", true, keys, type); + + final ResponseEntity response = testRestTemplate + .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(payload, headers), Void.class); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + private HttpHeaders headers() { final HttpHeaders headers = new HttpHeaders(); headers.add("cwa-fake", "0"); @@ -193,18 +225,16 @@ private HttpHeaders headers() { /** * Test Self-Report Submission (SRS). - * - * @param submissionTypeNumber - {@link SubmissionType} */ @ParameterizedTest - @ValueSource(ints = { 3, 4, 5, 6, 7, 8 }) // SUBMISSION_TYPE_SRS_* - void testSrsOtpAuth(int submissionTypeNumber) { + @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) + void testSrsOtpAuth(final SubmissionType type) { final List validKeys = createValidTemporaryExposureKeys(1); final HttpHeaders headers = headers(); headers.add("cwa-otp", "bar"); final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - validKeys, SubmissionType.forNumber(submissionTypeNumber)); + validKeys, type); final int before = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM self_report_submissions", Integer.class); From ea95d8a238ced68fd472009fc44b90f493379cb1 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 8 Nov 2022 14:27:30 +0100 Subject: [PATCH 13/83] fix spring-security issue --- pom.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pom.xml b/pom.xml index 6630ca4241..bf1cf75a13 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ 2.3.2 1.3.4 + 5.7.5 3.21.9 1.1.1 1.5.1 @@ -112,6 +113,26 @@ test + + org.springframework.security + spring-security-core + ${spring-security.version} + + + org.springframework.security + spring-security-config + ${spring-security.version} + + + org.springframework.security + spring-security-web + ${spring-security.version} + + + org.springframework.security + spring-security-crypto + ${spring-security.version} + org.springframework.boot spring-boot-dependencies From c27f796fa4f65f1f2d33b5994b57ff97bd2b310a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 9 Nov 2022 10:35:25 +0100 Subject: [PATCH 14/83] use same version as on OTC --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1f2e53b28e..53a233b130 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -138,7 +138,7 @@ services: volumes: - ./docker-compose-test-secrets:/secrets postgres: - image: postgres:11.8 + image: postgres:11.5 restart: always ports: - "8001:5432" From 2da827adffb09c63afaa91d3971bcb7e34a1a584 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 10 Nov 2022 11:38:36 +0100 Subject: [PATCH 15/83] revision=3.0.0 --- .mvn/maven.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index b5eba84acf..ffff32d3fc 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,4 +1,4 @@ --Drevision=2.29.0-SNAPSHOT +-Drevision=3.0.0-SNAPSHOT -Dlicense.projectName=Corona-Warn-App -Dlicense.inceptionYear=2020 -Dlicense.licenseName=apache_v2 From 8169cf6e8c6fc704d2e587d103a3c942af5302d1 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 10 Nov 2022 11:38:52 +0100 Subject: [PATCH 16/83] SQL --- ...reateSRSTables.sql => V24__featureSelfReportSubmissions.sql} | 2 -- .../db/specific/postgresql/V25__grantSRSPerimissions.sql | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) rename common/persistence/src/main/resources/db/migration/{V24__createSRSTables.sql => V24__featureSelfReportSubmissions.sql} (90%) create mode 100644 common/persistence/src/main/resources/db/specific/postgresql/V25__grantSRSPerimissions.sql diff --git a/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql b/common/persistence/src/main/resources/db/migration/V24__featureSelfReportSubmissions.sql similarity index 90% rename from common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql rename to common/persistence/src/main/resources/db/migration/V24__featureSelfReportSubmissions.sql index 40cd359c33..7afe84da1c 100644 --- a/common/persistence/src/main/resources/db/migration/V24__createSRSTables.sql +++ b/common/persistence/src/main/resources/db/migration/V24__featureSelfReportSubmissions.sql @@ -3,8 +3,6 @@ CREATE TABLE IF NOT EXISTS self_report_submissions ( submission_date DATE NOT NULL DEFAULT CURRENT_DATE ); -GRANT INSERT ON TABLE self_report_submissions TO "cwa_submission"; - CREATE OR REPLACE VIEW self_reports AS SELECT COUNT(*), submission_date, submission_type FROM self_report_submissions diff --git a/common/persistence/src/main/resources/db/specific/postgresql/V25__grantSRSPerimissions.sql b/common/persistence/src/main/resources/db/specific/postgresql/V25__grantSRSPerimissions.sql new file mode 100644 index 0000000000..9b5e085ea0 --- /dev/null +++ b/common/persistence/src/main/resources/db/specific/postgresql/V25__grantSRSPerimissions.sql @@ -0,0 +1,2 @@ +GRANT SELECT, INSERT ON TABLE self_report_submissions TO "cwa_submission"; +GRANT SELECT, DELETE ON TABLE self_report_submissions TO "cwa_distribution"; From 0d04ecae24689e819ef289a382d08e0aea2ba2f5 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 10 Nov 2022 15:36:43 +0100 Subject: [PATCH 17/83] Java 17 --- .github/workflows/maven-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 6c7df1c27e..91a12a8b57 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: temurin cache: maven - name: mvn verify From 3b3f6d9592c5c2f1a5fc4fd725242bf7daf7f6a5 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 10 Nov 2022 16:41:11 +0100 Subject: [PATCH 18/83] Java 17 --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0838dbde5b..768e3aa532 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,10 +24,10 @@ jobs: with: languages: java queries: security-extended - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: temurin cache: maven - name: Build From 7187026f7bb54ecd559f9cb06ffac54a3f8f5f8d Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 11 Nov 2022 15:39:18 +0100 Subject: [PATCH 19/83] pacify hadolint --- docs/how_to/HOW-TO-SUBMISSION.md | 4 ++-- services/callback/Dockerfile | 2 +- services/distribution/Dockerfile | 2 +- services/download/Dockerfile | 2 +- services/submission/Dockerfile | 2 +- services/upload/Dockerfile | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/how_to/HOW-TO-SUBMISSION.md b/docs/how_to/HOW-TO-SUBMISSION.md index ffaa15d6d1..f0dece676c 100644 --- a/docs/how_to/HOW-TO-SUBMISSION.md +++ b/docs/how_to/HOW-TO-SUBMISSION.md @@ -28,11 +28,11 @@ Content-Type | `application/x-protobuf` * Send the following request via Curl (replace ``with the actual path to the payload on your machine): ```bash -curl --location --request POST 'http://localhost:8080/version/v1/diagnosis-keys' \ +curl -k --location --request POST 'https://localhost:8080/version/v1/diagnosis-keys' \ --header 'CWA-Authorization: edc07f08-a1aa-11ea-bb37-0242ac130002' \ --header 'CWA-Fake: 0' \ --header 'Content-Type: application/x-protobuf' \ ---data-binary '' +--data-binary 'services/submission/src/test/resources/payload/mobile-client-payload.pb' ``` ## Generating a Payload diff --git a/services/callback/Dockerfile b/services/callback/Dockerfile index c3287f2bbd..c1b6bd0efe 100644 --- a/services/callback/Dockerfile +++ b/services/callback/Dockerfile @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java17-debian11 +FROM gcr.io/distroless/java17-debian11@sha256:61c05080a2fbf2f6fcb2eb11b936e30ebc7eb5cad474056e5e356b5b1b94d200 ARG SERVICE=callback diff --git a/services/distribution/Dockerfile b/services/distribution/Dockerfile index 22b3fe9753..cc81ab169e 100644 --- a/services/distribution/Dockerfile +++ b/services/distribution/Dockerfile @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java17-debian11 +FROM gcr.io/distroless/java17-debian11@sha256:61c05080a2fbf2f6fcb2eb11b936e30ebc7eb5cad474056e5e356b5b1b94d200 ARG SERVICE=distribution diff --git a/services/download/Dockerfile b/services/download/Dockerfile index e957c391dc..3ed71dfdd7 100644 --- a/services/download/Dockerfile +++ b/services/download/Dockerfile @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java17-debian11 +FROM gcr.io/distroless/java17-debian11@sha256:61c05080a2fbf2f6fcb2eb11b936e30ebc7eb5cad474056e5e356b5b1b94d200 ARG SERVICE=download diff --git a/services/submission/Dockerfile b/services/submission/Dockerfile index d54e7538ec..47303219b2 100644 --- a/services/submission/Dockerfile +++ b/services/submission/Dockerfile @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java17-debian11 +FROM gcr.io/distroless/java17-debian11@sha256:61c05080a2fbf2f6fcb2eb11b936e30ebc7eb5cad474056e5e356b5b1b94d200 ARG SERVICE=submission diff --git a/services/upload/Dockerfile b/services/upload/Dockerfile index e08df1a3f4..ea1fc2a168 100644 --- a/services/upload/Dockerfile +++ b/services/upload/Dockerfile @@ -34,7 +34,7 @@ RUN ${WORK_DIR}/mvnw --batch-mode -P docker-image --file ${WORK_DIR}/pom.xml ${M RUN cp ${WORK_DIR}/services/${SERVICE}/target/*.jar /usr/sap/${SERVICE}-service/service.jar RUN cp ${WORK_DIR}/scripts/DpkgHelper.java /DpkgHelper.java -FROM gcr.io/distroless/java17-debian11 +FROM gcr.io/distroless/java17-debian11@sha256:61c05080a2fbf2f6fcb2eb11b936e30ebc7eb5cad474056e5e356b5b1b94d200 ARG SERVICE=upload From 1eeee4f1926f4ced7a3a0b012da8567ab4dda5c6 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 15 Nov 2022 16:13:47 +0100 Subject: [PATCH 20/83] remove xmlns reduces external dependency risks --- common/federation/pom.xml | 2 +- common/jupiterHelpers/pom.xml | 2 +- common/persistence/pom.xml | 2 +- common/pom.xml | 2 +- common/protocols/pom.xml | 2 +- common/shared/pom.xml | 2 +- pom.xml | 2 +- services/callback/pom.xml | 2 +- services/distribution/pom.xml | 2 +- services/download/pom.xml | 2 +- services/pom.xml | 2 +- services/submission/pom.xml | 2 +- services/upload/pom.xml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/common/federation/pom.xml b/common/federation/pom.xml index 77b6833822..fa181405db 100644 --- a/common/federation/pom.xml +++ b/common/federation/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/common/jupiterHelpers/pom.xml b/common/jupiterHelpers/pom.xml index e8aafdfdfe..dbd20c6ca4 100644 --- a/common/jupiterHelpers/pom.xml +++ b/common/jupiterHelpers/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/common/persistence/pom.xml b/common/persistence/pom.xml index 8f719ebd7d..51f2a58675 100644 --- a/common/persistence/pom.xml +++ b/common/persistence/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/common/pom.xml b/common/pom.xml index 1ba2310e9c..bb621b5d3b 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/common/protocols/pom.xml b/common/protocols/pom.xml index 6d626c84f5..7523d10bc8 100644 --- a/common/protocols/pom.xml +++ b/common/protocols/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/common/shared/pom.xml b/common/shared/pom.xml index 99399ef367..f47c4567cb 100644 --- a/common/shared/pom.xml +++ b/common/shared/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/pom.xml b/pom.xml index 7517c6eeeb..f364f7ac7e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 app.coronawarn.server diff --git a/services/callback/pom.xml b/services/callback/pom.xml index 7bb88bd134..651837881d 100644 --- a/services/callback/pom.xml +++ b/services/callback/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/services/distribution/pom.xml b/services/distribution/pom.xml index 67c0a5820b..0b801e08a8 100644 --- a/services/distribution/pom.xml +++ b/services/distribution/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/services/download/pom.xml b/services/download/pom.xml index 92ccece2ea..c1709b11fb 100644 --- a/services/download/pom.xml +++ b/services/download/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/services/pom.xml b/services/pom.xml index 45d67eb1cc..1ff9bb1c5d 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/services/submission/pom.xml b/services/submission/pom.xml index d5241c2733..fd574be323 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 diff --git a/services/upload/pom.xml b/services/upload/pom.xml index dda4e7b117..02afd94521 100644 --- a/services/upload/pom.xml +++ b/services/upload/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 From d36007d2aced3ea50beda114df72f8fb46ab4c67 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 21 Nov 2022 16:22:34 +0100 Subject: [PATCH 21/83] prepare SRS daily limit and retention --- .../repository/DiagnosisKeyRepository.java | 27 +++++++++++++++++++ .../DiagnosisKeyRepositoryTest.java | 18 ++++++++++--- 2 files changed, 42 insertions(+), 3 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 add740208b..15fd6d9bc0 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 @@ -1,6 +1,7 @@ package app.coronawarn.server.common.persistence.repository; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; +import java.time.LocalDate; import java.util.List; import org.springframework.data.jdbc.repository.query.Modifying; import org.springframework.data.jdbc.repository.query.Query; @@ -102,4 +103,30 @@ List findAllWithTrlGreaterThanOrEqual(final @Param("minTrl") int m + "(submission_type) " + "VALUES (:submission_type) ") boolean recordSrs(final @Param("submission_type") String submissionType); + + /** + * Counts all entries of 'self_report_submissions' for today. + * + * @return The number of submitted self reports for today. + */ + @Query("SELECT COUNT(*) FROM self_report_submissions WHERE submission_date = CURRENT_DATE") + int countTodaysSrs(); + + /** + * Counts all entries of 'self_report_submissions' that have a submission date older than the specified one. + * + * @param submissionDate The submission date up to which entries will be expired. + * @return The number of expired keys. + */ + @Query("SELECT COUNT(*) FROM self_report_submissions WHERE submission_date<:threshold") + int countSrsOlderThan(final @Param("threshold") LocalDate submissionDate); + + /** + * Deletes all entries from 'self_report_submissions' that have a submission timestamp older than the specified one. + * + * @param submissionDate The submission date up to which entries will be deleted. + */ + @Modifying + @Query("DELETE FROM self_report_submissions WHERE submission_date<:threshold") + void deleteSrsOlderThan(final @Param("threshold") LocalDate submissionDate); } diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java index 65bc33f828..0c223051e8 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java @@ -1,15 +1,20 @@ package app.coronawarn.server.common.persistence.repository; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.protocols.external.exposurenotification.ReportType; import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; +import java.time.LocalDate; import java.util.Random; import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; @@ -53,8 +58,15 @@ void shouldCheckExistence() { assertTrue(repository.exists(id, type.name())); } - @Test - void recordSRSTest() { - assertTrue(repository.recordSrs(SubmissionType.SUBMISSION_TYPE_SRS_RAPID_PCR.name())); + @ParameterizedTest + @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) + void recordSrsTest(final SubmissionType type) { + assertTrue(repository.recordSrs(type.name())); + assertEquals(1, repository.countTodaysSrs()); + LocalDate tomorrow = LocalDate.now().plusDays(1); + assertEquals(1, repository.countSrsOlderThan(tomorrow)); + repository.deleteSrsOlderThan(tomorrow); + assertEquals(0, repository.countTodaysSrs()); + assertEquals(0, repository.countSrsOlderThan(tomorrow)); } } From 88ad6395faad91900187e15cc645a3b94d906c34 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 22 Nov 2022 13:10:51 +0100 Subject: [PATCH 22/83] draft diagnosisKeyService SRS retention policy --- .../repository/DiagnosisKeyRepository.java | 8 +- .../service/DiagnosisKeyService.java | 149 ++++++++++-------- .../service/DiagnosisKeyServiceTest.java | 65 +++++--- .../distribution/runner/RetentionPolicy.java | 1 + 4 files changed, 132 insertions(+), 91 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 15fd6d9bc0..18628e3624 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 @@ -99,9 +99,7 @@ List findAllWithTrlGreaterThanOrEqual(final @Param("minTrl") int m * @return true, when insert into self_report_submissions was successful */ @Modifying - @Query("INSERT INTO self_report_submissions " - + "(submission_type) " - + "VALUES (:submission_type) ") + @Query("INSERT INTO self_report_submissions (submission_type) VALUES (:submission_type)") boolean recordSrs(final @Param("submission_type") String submissionType); /** @@ -118,7 +116,7 @@ List findAllWithTrlGreaterThanOrEqual(final @Param("minTrl") int m * @param submissionDate The submission date up to which entries will be expired. * @return The number of expired keys. */ - @Query("SELECT COUNT(*) FROM self_report_submissions WHERE submission_date<:threshold") + @Query("SELECT COUNT(*) FROM self_report_submissions WHERE submission_date <= :threshold") int countSrsOlderThan(final @Param("threshold") LocalDate submissionDate); /** @@ -127,6 +125,6 @@ List findAllWithTrlGreaterThanOrEqual(final @Param("minTrl") int m * @param submissionDate The submission date up to which entries will be deleted. */ @Modifying - @Query("DELETE FROM self_report_submissions WHERE submission_date<:threshold") + @Query("DELETE FROM self_report_submissions WHERE submission_date <= :threshold") void deleteSrsOlderThan(final @Param("threshold") LocalDate submissionDate); } diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java index 10132f4972..2a7f199838 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java @@ -10,6 +10,7 @@ import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; import io.micrometer.core.annotation.Timed; import java.time.Instant; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -25,12 +26,85 @@ public class DiagnosisKeyService { private static final Logger logger = LoggerFactory.getLogger(DiagnosisKeyService.class); + + /** + * Calculates epoch seconds in UTC based upon current time and given days. + * + * @param daysToRetain offset in days to use for epoch second + * @return epoch seconds + */ + public static long daysToSeconds(final int daysToRetain) { + if (daysToRetain < 0) { + throw new IllegalArgumentException("Number of days to retain must be greater or equal to 0."); + } + + return LocalDateTime + .ofInstant(Instant.now(), UTC) + .minusDays(daysToRetain) + .toEpochSecond(UTC) / SECONDS_PER_HOUR; + } + private final DiagnosisKeyRepository keyRepository; + private final ValidDiagnosisKeyFilter validationFilter; - public DiagnosisKeyService(DiagnosisKeyRepository keyRepository, ValidDiagnosisKeyFilter filter) { + public DiagnosisKeyService(final DiagnosisKeyRepository keyRepository, final ValidDiagnosisKeyFilter filter) { this.keyRepository = keyRepository; - this.validationFilter = filter; + validationFilter = filter; + } + + /** + * Deletes all diagnosis key entries which have a submission timestamp that is older than the specified number of + * days. + * + * @param daysToRetain the number of days until which diagnosis keys will be retained. + * @throws IllegalArgumentException if {@code daysToRetain} is negative. + */ + @Transactional + public void applyRetentionPolicy(final int daysToRetain) { + final long threshold = daysToSeconds(daysToRetain); + final int numberOfDeletions = keyRepository.countOlderThan(threshold); + logger.info("Deleting {} diagnosis key(s) with a submission timestamp older than {} day(s) ago.", + numberOfDeletions, daysToRetain); + keyRepository.deleteOlderThan(threshold); + } + + /** + * Delete entries from 'self_report_submissions', which are older than given days. + * + * @param retentionDays - How many days should be kept in DB? + */ + @Transactional + public void applySrsRetentionPolicy(final int retentionDays) { + final LocalDate retentionDate = LocalDate.now(UTC).minusDays(retentionDays); + final int numberOfDeletions = keyRepository.countSrsOlderThan(retentionDate); + logger.info("Deleting {} SRS with a submission date older than {} day(s) ago.", + numberOfDeletions, retentionDays); + keyRepository.deleteSrsOlderThan(retentionDate); + } + + /** + * Returns all valid persisted diagnosis keys, sorted by their submission timestamp. + * + * @return ValidDiagnosisKeyFilter + */ + public List getDiagnosisKeys() { + final List diagnosisKeys = createStreamFromIterator( + keyRepository.findAll(Sort.by(Direction.ASC, "submissionTimestamp")).iterator()).collect(Collectors.toList()); + return validationFilter.filter(diagnosisKeys); + } + + /** + * Fetches {@link DiagnosisKey}s from DB with TRL greater or equal than the given value. + * + * @param minTrl Minimum Transmission-Risk-Level to fetch. + * @param daysToFetch time in days, that should be published + * @return List of {@link DiagnosisKey}s filtered by {@link #validationFilter}. + */ + public List getDiagnosisKeysWithMinTrl(final int minTrl, final int daysToFetch) { + final List diagnosisKeys = keyRepository.findAllWithTrlGreaterThanOrEqual(minTrl, + daysToSeconds(daysToFetch)); + return validationFilter.filter(diagnosisKeys); } @Timed @@ -39,6 +113,12 @@ public boolean recordSrs(final SubmissionType submissionType) { return keyRepository.recordSrs(submissionType.name()); } + @Timed + @Transactional + public int countTodaysSrs() { + return keyRepository.countTodaysSrs(); + } + /** * Persists the specified collection of {@link DiagnosisKey} instances and returns the number of inserted diagnosis * keys. If the key data of a particular diagnosis key already exists in the database and is of a submission type that @@ -51,16 +131,16 @@ public boolean recordSrs(final SubmissionType submissionType) { */ @Timed @Transactional - public int saveDiagnosisKeys(Collection diagnosisKeys) { + public int saveDiagnosisKeys(final Collection diagnosisKeys) { int numberOfInsertedKeys = 0; - for (DiagnosisKey diagnosisKey : diagnosisKeys) { + for (final DiagnosisKey diagnosisKey : diagnosisKeys) { if (keyRepository.exists(diagnosisKey.getKeyData(), SubmissionType.SUBMISSION_TYPE_PCR_TEST.name())) { continue; } - boolean keyInsertedSuccessfully = keyRepository.saveDoNothingOnConflict( + final boolean keyInsertedSuccessfully = keyRepository.saveDoNothingOnConflict( diagnosisKey.getKeyData(), diagnosisKey.getRollingStartIntervalNumber(), diagnosisKey.getRollingPeriod(), diagnosisKey.getSubmissionTimestamp(), diagnosisKey.getTransmissionRiskLevel(), diagnosisKey.getOriginCountry(), diagnosisKey.getVisitedCountries().toArray(new String[0]), @@ -72,7 +152,7 @@ public int saveDiagnosisKeys(Collection diagnosisKeys) { } } - int conflictingKeys = diagnosisKeys.size() - numberOfInsertedKeys; + final int conflictingKeys = diagnosisKeys.size() - numberOfInsertedKeys; if (conflictingKeys > 0) { logger.warn("{} out of {} diagnosis keys conflicted with existing database entries and were ignored.", conflictingKeys, diagnosisKeys.size()); @@ -80,61 +160,4 @@ public int saveDiagnosisKeys(Collection diagnosisKeys) { return numberOfInsertedKeys; } - - /** - * Returns all valid persisted diagnosis keys, sorted by their submission timestamp. - * - * @return ValidDiagnosisKeyFilter - */ - public List getDiagnosisKeys() { - List diagnosisKeys = createStreamFromIterator( - keyRepository.findAll(Sort.by(Direction.ASC, "submissionTimestamp")).iterator()).collect(Collectors.toList()); - return validationFilter.filter(diagnosisKeys); - } - - /** - * Fetches {@link DiagnosisKey}s from DB with TRL greater or equal than the given value. - * - * @param minTrl Minimum Transmission-Risk-Level to fetch. - * @param daysToFetch time in days, that should be published - * @return List of {@link DiagnosisKey}s filtered by {@link #validationFilter}. - */ - public List getDiagnosisKeysWithMinTrl(final int minTrl, final int daysToFetch) { - final List diagnosisKeys = keyRepository.findAllWithTrlGreaterThanOrEqual(minTrl, - daysToSeconds(daysToFetch)); - return validationFilter.filter(diagnosisKeys); - } - - /** - * Deletes all diagnosis key entries which have a submission timestamp that is older than the specified number of - * days. - * - * @param daysToRetain the number of days until which diagnosis keys will be retained. - * @throws IllegalArgumentException if {@code daysToRetain} is negative. - */ - @Transactional - public void applyRetentionPolicy(int daysToRetain) { - final long threshold = daysToSeconds(daysToRetain); - int numberOfDeletions = keyRepository.countOlderThan(threshold); - logger.info("Deleting {} diagnosis key(s) with a submission timestamp older than {} day(s) ago.", - numberOfDeletions, daysToRetain); - keyRepository.deleteOlderThan(threshold); - } - - /** - * Calculates epoch seconds in UTC based upon current time and given days. - * - * @param daysToRetain offset in days to use for epoch second - * @return epoch seconds - */ - public static long daysToSeconds(final int daysToRetain) { - if (daysToRetain < 0) { - throw new IllegalArgumentException("Number of days to retain must be greater or equal to 0."); - } - - return LocalDateTime - .ofInstant(Instant.now(), UTC) - .minusDays(daysToRetain) - .toEpochSecond(UTC) / SECONDS_PER_HOUR; - } } diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java index bdaa3f0b1b..accd230dce 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java @@ -4,15 +4,18 @@ import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.assertDiagnosisKeysEqual; import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.buildDiagnosisKeyForDateTime; import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.buildDiagnosisKeyForSubmissionTimestamp; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_PCR_TEST; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_RAPID_TEST; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_OTHER; import static java.time.ZoneOffset.UTC; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.util.Lists.list; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.persistence.repository.DiagnosisKeyRepository; @@ -56,10 +59,10 @@ void testRetrievalForEmptyDB() { @Test void testSaveAndRetrieve() { var expKeys = List.of( - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(false, 1, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(false, 1, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(true, 1, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(true, 1, SubmissionType.SUBMISSION_TYPE_RAPID_TEST) + DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(false, 1, SUBMISSION_TYPE_PCR_TEST), + DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(false, 1, SUBMISSION_TYPE_RAPID_TEST), + DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST), + DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_RAPID_TEST) ); diagnosisKeyService.saveDiagnosisKeys(expKeys); @@ -73,23 +76,23 @@ void testSaveAndRetrieve() { void testSaveAndRetrieveKeysFilteredByTrl() { var filterOutKeysBasedOnTrl = List.of( DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(false, daysToSeconds(1), - SubmissionType.SUBMISSION_TYPE_PCR_TEST, 1), + SUBMISSION_TYPE_PCR_TEST, 1), DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(false, daysToSeconds(1), - SubmissionType.SUBMISSION_TYPE_RAPID_TEST, 2) + SUBMISSION_TYPE_RAPID_TEST, 2) ); var expKeys = List.of( DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(1), - SubmissionType.SUBMISSION_TYPE_PCR_TEST, 3), + SUBMISSION_TYPE_PCR_TEST, 3), DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(1), - SubmissionType.SUBMISSION_TYPE_RAPID_TEST, 4) + SUBMISSION_TYPE_RAPID_TEST, 4) ); var oldKeys = List.of( DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(42), - SubmissionType.SUBMISSION_TYPE_PCR_TEST, 3), + SUBMISSION_TYPE_PCR_TEST, 3), DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(42), - SubmissionType.SUBMISSION_TYPE_RAPID_TEST, 4) + SUBMISSION_TYPE_RAPID_TEST, 4) ); diagnosisKeyService.saveDiagnosisKeys(filterOutKeysBasedOnTrl); @@ -208,9 +211,9 @@ void testReturnedNumberOfInsertedKeysForNoConflict() { @Test void insertsPcrTestDiagnosisKeysWhenRapidTestIsPresent() { DiagnosisKey pcrKey = DiagnosisKeyServiceTestHelper - .generateRandomDiagnosisKey(true, 1, SubmissionType.SUBMISSION_TYPE_PCR_TEST); + .generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST); DiagnosisKey rapidKey = DiagnosisKey.builder() - .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SubmissionType.SUBMISSION_TYPE_RAPID_TEST) + .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SUBMISSION_TYPE_RAPID_TEST) .withRollingStartIntervalNumber(pcrKey.getRollingStartIntervalNumber()) .withTransmissionRiskLevel(pcrKey.getTransmissionRiskLevel()) .withConsentToFederation(pcrKey.isConsentToFederation()) @@ -236,9 +239,14 @@ void insertsPcrTestDiagnosisKeysWhenRapidTestIsPresent() { @Test void ignoresRapidTestDiagnosisKeysWhenPcrTestIsPresent() { DiagnosisKey pcrKey = DiagnosisKeyServiceTestHelper - .generateRandomDiagnosisKey(true, 1, SubmissionType.SUBMISSION_TYPE_PCR_TEST); - DiagnosisKey rapidKey = DiagnosisKey.builder() - .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SubmissionType.SUBMISSION_TYPE_RAPID_TEST) + .generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST); + Collection storedKeys; + diagnosisKeyService.saveDiagnosisKeys(List.of(pcrKey)); + storedKeys = diagnosisKeyService.getDiagnosisKeys(); + assertEquals(1, storedKeys.size()); + assertTrue(storedKeys.contains(pcrKey)); + final DiagnosisKey rapidKey = DiagnosisKey.builder() + .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SUBMISSION_TYPE_RAPID_TEST) .withRollingStartIntervalNumber(pcrKey.getRollingStartIntervalNumber()) .withTransmissionRiskLevel(pcrKey.getTransmissionRiskLevel()) .withConsentToFederation(pcrKey.isConsentToFederation()) @@ -249,11 +257,6 @@ void ignoresRapidTestDiagnosisKeysWhenPcrTestIsPresent() { .withSubmissionTimestamp(pcrKey.getSubmissionTimestamp()) .withVisitedCountries(pcrKey.getVisitedCountries()) .build(); - Collection storedKeys; - diagnosisKeyService.saveDiagnosisKeys(List.of(pcrKey)); - storedKeys = diagnosisKeyService.getDiagnosisKeys(); - assertEquals(1, storedKeys.size()); - assertTrue(storedKeys.contains(pcrKey)); diagnosisKeyService.saveDiagnosisKeys(List.of(rapidKey)); storedKeys = diagnosisKeyService.getDiagnosisKeys(); assertEquals(1, storedKeys.size()); @@ -266,4 +269,20 @@ void ignoresRapidTestDiagnosisKeysWhenPcrTestIsPresent() { void recordSrsTest(final SubmissionType type) { diagnosisKeyService.recordSrs(type); } + + /** + * record 3 self reports for today, remove all older than yesterday, check that the ones from today are still there, + * delete also from today, finally check that there are 0 for today. + */ + @Test + void srsTests() { + diagnosisKeyService.recordSrs(SUBMISSION_TYPE_SRS_OTHER); + diagnosisKeyService.recordSrs(SUBMISSION_TYPE_SRS_OTHER); + diagnosisKeyService.recordSrs(SUBMISSION_TYPE_SRS_OTHER); + assertEquals(3, diagnosisKeyService.countTodaysSrs()); + diagnosisKeyService.applySrsRetentionPolicy(1); + assertEquals(3, diagnosisKeyService.countTodaysSrs()); + diagnosisKeyService.applySrsRetentionPolicy(0); + assertEquals(0, diagnosisKeyService.countTodaysSrs()); + } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java index 50f857761a..d2085cfd65 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java @@ -66,6 +66,7 @@ public RetentionPolicy( public void run(ApplicationArguments args) { try { diagnosisKeyService.applyRetentionPolicy(retentionDays); + diagnosisKeyService.applySrsRetentionPolicy(retentionDays /* FIXME - same amount? */); traceTimeIntervalWarningService.applyRetentionPolicy(retentionDays); s3RetentionPolicy.applyDiagnosisKeyDayRetentionPolicy(retentionDays); s3RetentionPolicy.applyDiagnosisKeyHourRetentionPolicy(hourFileRetentionDays); From 1ba8f1e061c603eb6322cd08608d2ebcd9e5e7e4 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 24 Nov 2022 21:15:49 +0100 Subject: [PATCH 23/83] Mobile-App-Config setSelfReportParameters --- ...ationConfigurationV2PublicationConfig.java | 198 +++++++++++------- .../config/DistributionServiceConfig.java | 37 ++++ .../src/main/resources/application.yaml | 7 + .../config/DistributionServiceConfigTest.java | 18 ++ .../src/test/resources/application.yaml | 8 +- 5 files changed, 186 insertions(+), 82 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationV2PublicationConfig.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationV2PublicationConfig.java index 0f3169444a..e8ac7c02fc 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationV2PublicationConfig.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/ApplicationConfigurationV2PublicationConfig.java @@ -1,5 +1,8 @@ package app.coronawarn.server.services.distribution.assembly.appconfig; +import static app.coronawarn.server.services.distribution.assembly.appconfig.YamlLoader.loadYamlIntoClass; +import static app.coronawarn.server.services.distribution.assembly.appconfig.YamlLoader.loadYamlIntoProtobufBuilder; + import app.coronawarn.server.common.persistence.domain.config.PreDistributionTrlValueMappingProvider; import app.coronawarn.server.common.protocols.internal.v2.AppFeature; import app.coronawarn.server.common.protocols.internal.v2.AppFeatures; @@ -30,8 +33,10 @@ import app.coronawarn.server.common.protocols.internal.v2.PPDDPrivacyPreservingAnalyticsParametersAndroid; import app.coronawarn.server.common.protocols.internal.v2.PPDDPrivacyPreservingAnalyticsParametersCommon; import app.coronawarn.server.common.protocols.internal.v2.PPDDPrivacyPreservingAnalyticsParametersIOS; +import app.coronawarn.server.common.protocols.internal.v2.PPDDSelfReportSubmissionParametersAndroid; +import app.coronawarn.server.common.protocols.internal.v2.PPDDSelfReportSubmissionParametersCommon; +import app.coronawarn.server.common.protocols.internal.v2.PPDDSelfReportSubmissionParametersIOS; import app.coronawarn.server.common.protocols.internal.v2.PresenceTracingParameters; -import app.coronawarn.server.common.protocols.internal.v2.PresenceTracingParameters.Builder; import app.coronawarn.server.common.protocols.internal.v2.RiskCalculationParameters; import app.coronawarn.server.common.protocols.internal.v2.SemanticVersion; import app.coronawarn.server.common.shared.exception.UnableToLoadFileException; @@ -46,6 +51,7 @@ import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidExposureDetectionParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidKeyDownloadParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidPrivacyPreservingAnalyticsParameters; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidSrsPpacParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.DeserializedDayPackageMetadata; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.DeserializedHourPackageMetadata; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.DgcParameters.DgcBlocklistParameters.DgcBlockedUvciChunk; @@ -54,6 +60,7 @@ import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.IosKeyDownloadParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.IosPrivacyPreservingAnalyticsParameters; import com.google.protobuf.ByteString; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Qualifier; @@ -96,18 +103,17 @@ public ApplicationConfigurationAndroid createAndroidV1Configuration( DistributionServiceConfig distributionServiceConfig, PreDistributionTrlValueMappingProvider trlValueMappingProvider) throws UnableToLoadFileException { - RiskCalculationParameters.Builder riskCalculationParameterBuilder = - YamlLoader.loadYamlIntoProtobufBuilder(V1_RISK_PARAMETERS_FILE, - RiskCalculationParameters.Builder.class); + RiskCalculationParameters.Builder riskCalculationParameterBuilder = loadYamlIntoProtobufBuilder( + V1_RISK_PARAMETERS_FILE, + RiskCalculationParameters.Builder.class); riskCalculationParameterBuilder.addAllTransmissionRiskValueMapping( trlValueMappingProvider.getTransmissionRiskValueMappingAsProto()); - CoronaTestParameters.Builder coronaTestParameters = - YamlLoader.loadYamlIntoProtobufBuilder(CORONA_TEST_PARAMETERS_FILE, - CoronaTestParameters.Builder.class); + CoronaTestParameters.Builder coronaTestParameters = loadYamlIntoProtobufBuilder(CORONA_TEST_PARAMETERS_FILE, + CoronaTestParameters.Builder.class); - DeserializedDiagnosisKeysDataMapping dataMapping = YamlLoader.loadYamlIntoClass( + DeserializedDiagnosisKeysDataMapping dataMapping = loadYamlIntoClass( ANDROID_V2_DATA_MAPPING_FILE, DeserializedDiagnosisKeysDataMapping.class); DeserializedDailySummariesConfig dailySummaries = YamlLoader @@ -129,6 +135,7 @@ public ApplicationConfigurationAndroid createAndroidV1Configuration( .setPrivacyPreservingAnalyticsParameters(buildAndroidPpaParameters(distributionServiceConfig)) .setPresenceTracingParameters(buildPresenceTracingParameters(distributionServiceConfig)) .setCoronaTestParameters(coronaTestParameters) + .setSelfReportParameters(buildPpddSelfReportSubmissionParametersAndroid(distributionServiceConfig)) .build(); } @@ -145,18 +152,18 @@ public ApplicationConfigurationAndroid createAndroidV2Configuration( DistributionServiceConfig distributionServiceConfig, PreDistributionTrlValueMappingProvider trlValueMappingProvider) throws UnableToLoadFileException { - RiskCalculationParameters.Builder riskCalculationParameterBuilder = - YamlLoader.loadYamlIntoProtobufBuilder(V2_RISK_PARAMETERS_FILE, - RiskCalculationParameters.Builder.class); + RiskCalculationParameters.Builder riskCalculationParameterBuilder = loadYamlIntoProtobufBuilder( + V2_RISK_PARAMETERS_FILE, + RiskCalculationParameters.Builder.class); riskCalculationParameterBuilder.addAllTransmissionRiskValueMapping( trlValueMappingProvider.getTransmissionRiskValueMappingAsProto()); - CoronaTestParameters.Builder coronaTestParameters = - YamlLoader.loadYamlIntoProtobufBuilder(CORONA_TEST_PARAMETERS_FILE, - CoronaTestParameters.Builder.class); + CoronaTestParameters.Builder coronaTestParameters = loadYamlIntoProtobufBuilder( + CORONA_TEST_PARAMETERS_FILE, + CoronaTestParameters.Builder.class); - DeserializedDiagnosisKeysDataMapping dataMapping = YamlLoader.loadYamlIntoClass( + DeserializedDiagnosisKeysDataMapping dataMapping = loadYamlIntoClass( ANDROID_V2_DATA_MAPPING_FILE, DeserializedDiagnosisKeysDataMapping.class); DeserializedDailySummariesConfig dailySummaries = YamlLoader @@ -181,36 +188,34 @@ public ApplicationConfigurationAndroid createAndroidV2Configuration( .setCoronaTestParameters(coronaTestParameters) .setDgcParameters(buildDgcParameters(distributionServiceConfig, distributionServiceConfig .getAppConfigParameters().getDgcParameters().getAndroidDgcReissueServicePublicKeyDigest())) + .setSelfReportParameters(buildPpddSelfReportSubmissionParametersAndroid(distributionServiceConfig)) .build(); } - private Builder buildPresenceTracingParameters(DistributionServiceConfig distributionServiceConfig) + private PresenceTracingParameters buildPresenceTracingParameters(final DistributionServiceConfig config) throws UnableToLoadFileException { - PresenceTracingParameters.Builder presenceTracingParameters = - YamlLoader.loadYamlIntoProtobufBuilder(PRESENCE_TRACING_PARAMETERS_FILE, - PresenceTracingParameters.Builder.class); - DeserializedPlausibleDeniabilityParameters deserializedPlausibleDeniabilityParameters = YamlLoader - .loadYamlIntoClass( - PLAUSIBLE_DENIABILITY_PARAMETERS, DeserializedPlausibleDeniabilityParameters.class); - - DeserializedRevokedTraceLocationVersions deserializedRevokedTraceLocationVersions = YamlLoader.loadYamlIntoClass( + final PresenceTracingParameters.Builder builder = loadYamlIntoProtobufBuilder(PRESENCE_TRACING_PARAMETERS_FILE, + PresenceTracingParameters.Builder.class); + final DeserializedPlausibleDeniabilityParameters plausibleDeniabilityParameters = loadYamlIntoClass( + PLAUSIBLE_DENIABILITY_PARAMETERS, DeserializedPlausibleDeniabilityParameters.class); + + final DeserializedRevokedTraceLocationVersions deserializedRevokedTraceLocationVersions = loadYamlIntoClass( REVOKED_TRACE_LOCATION, DeserializedRevokedTraceLocationVersions.class); - presenceTracingParameters + return builder .setQrCodeErrorCorrectionLevelValue( - distributionServiceConfig.getPresenceTracingParameters().getQrCodeErrorCorrectionLevel()) + config.getPresenceTracingParameters().getQrCodeErrorCorrectionLevel()) .addAllRevokedTraceLocationVersions(deserializedRevokedTraceLocationVersions.getRevokedTraceLocationVersions()) - .setPlausibleDeniabilityParameters(presenceTracingParameters.getPlausibleDeniabilityParameters().toBuilder() - .addAllCheckInSizesInBytes(deserializedPlausibleDeniabilityParameters.getCheckInSizesInBytes()) + .setPlausibleDeniabilityParameters(builder.getPlausibleDeniabilityParameters().toBuilder() + .addAllCheckInSizesInBytes(plausibleDeniabilityParameters.getCheckInSizesInBytes()) .setProbabilityToFakeCheckInsIfNoCheckIns( - distributionServiceConfig.getPresenceTracingParameters().getPlausibleDeniabilityParameters() + config.getPresenceTracingParameters().getPlausibleDeniabilityParameters() .getProbabilityToFakeCheckInsIfNoCheckIns()) .setProbabilityToFakeCheckInsIfSomeCheckIns( - distributionServiceConfig.getPresenceTracingParameters().getPlausibleDeniabilityParameters() + config.getPresenceTracingParameters().getPlausibleDeniabilityParameters() .getProbabilityToFakeCheckInsIfSomeCheckIns()) .build()) .build(); - return presenceTracingParameters; } private PPDDErrorLogSharingParametersAndroid buildErrorLogSharingParametersAndroid( @@ -290,18 +295,17 @@ private DailySummariesConfig buildDailySummaries( .putAllInfectiousnessWeights(dailySummaries.getInfectiousnessWeights()).build(); } - private AppFeatures buildAppFeatures(DistributionServiceConfig distributionServiceConfig) { - List v2Features = distributionServiceConfig - .getAppFeatures().stream().map(feature -> AppFeature.newBuilder() - .setLabel(feature.getLabel()).setValue(feature.getValue()).build()) + private AppFeatures buildAppFeatures(final DistributionServiceConfig config) { + final Collection v2Features = config.getAppFeatures().stream() + .map(feature -> AppFeature.newBuilder().setLabel(feature.getLabel()).setValue(feature.getValue()).build()) .collect(Collectors.toList()); return AppFeatures.newBuilder().addAllAppFeatures(v2Features).build(); } private KeyDownloadParametersAndroid buildKeyDownloadParametersAndroid( DistributionServiceConfig distributionServiceConfig) { - AndroidKeyDownloadParameters androidKeyDownloadParameters = - distributionServiceConfig.getAppConfigParameters().getAndroidKeyDownloadParameters(); + AndroidKeyDownloadParameters androidKeyDownloadParameters = distributionServiceConfig.getAppConfigParameters() + .getAndroidKeyDownloadParameters(); return KeyDownloadParametersAndroid.newBuilder() .setOverallTimeoutInSeconds(androidKeyDownloadParameters.getOverallTimeoutInSeconds()) .setDownloadTimeoutInSeconds(androidKeyDownloadParameters.getDownloadTimeoutInSeconds()) @@ -314,8 +318,8 @@ private KeyDownloadParametersAndroid buildKeyDownloadParametersAndroid( private KeyDownloadParametersIOS buildKeyDownloadParametersIos( DistributionServiceConfig distributionServiceConfig) { - IosKeyDownloadParameters iosKeyDownloadParameters = - distributionServiceConfig.getAppConfigParameters().getIosKeyDownloadParameters(); + IosKeyDownloadParameters iosKeyDownloadParameters = distributionServiceConfig.getAppConfigParameters() + .getIosKeyDownloadParameters(); return KeyDownloadParametersIOS.newBuilder() .addAllRevokedDayPackages(buildRevokedDayPackages( iosKeyDownloadParameters.getRevokedDayPackages())) @@ -337,18 +341,18 @@ public ApplicationConfigurationIOS createIosV1Configuration(DistributionServiceC PreDistributionTrlValueMappingProvider trlValueMappingProvider) throws UnableToLoadFileException { - RiskCalculationParameters.Builder riskCalculationParameterBuilder = - YamlLoader.loadYamlIntoProtobufBuilder(V1_RISK_PARAMETERS_FILE, - RiskCalculationParameters.Builder.class); + RiskCalculationParameters.Builder riskCalculationParameterBuilder = loadYamlIntoProtobufBuilder( + V1_RISK_PARAMETERS_FILE, + RiskCalculationParameters.Builder.class); riskCalculationParameterBuilder.addAllTransmissionRiskValueMapping( trlValueMappingProvider.getTransmissionRiskValueMappingAsProto()); - CoronaTestParameters.Builder coronaTestParameters = - YamlLoader.loadYamlIntoProtobufBuilder(CORONA_TEST_PARAMETERS_FILE, - CoronaTestParameters.Builder.class); + CoronaTestParameters.Builder coronaTestParameters = loadYamlIntoProtobufBuilder( + CORONA_TEST_PARAMETERS_FILE, + CoronaTestParameters.Builder.class); - DeserializedExposureConfiguration exposureConfiguration = YamlLoader.loadYamlIntoClass( + DeserializedExposureConfiguration exposureConfiguration = loadYamlIntoClass( IOS_V2_EXPOSURE_CONFIGURATION_FILE, DeserializedExposureConfiguration.class); return ApplicationConfigurationIOS.newBuilder() @@ -365,6 +369,40 @@ public ApplicationConfigurationIOS createIosV1Configuration(DistributionServiceC .setPrivacyPreservingAnalyticsParameters(buildIosPpaParameters(distributionServiceConfig)) .setPresenceTracingParameters(buildPresenceTracingParameters(distributionServiceConfig)) .setCoronaTestParameters(coronaTestParameters) + .setSelfReportParameters(buildPpddSelfReportSubmissionParametersIos(distributionServiceConfig)) + .build(); + } + + private PPDDSelfReportSubmissionParametersIOS buildPpddSelfReportSubmissionParametersIos( + final DistributionServiceConfig config) { + return PPDDSelfReportSubmissionParametersIOS + .newBuilder().setCommon(buildPpddSelfReportSubmissionParametersCommon(config)).build(); + } + + private PPDDSelfReportSubmissionParametersCommon buildPpddSelfReportSubmissionParametersCommon( + final DistributionServiceConfig config) { + return PPDDSelfReportSubmissionParametersCommon.newBuilder() + .setTimeBetweenSubmissionsInDays(config.getAppConfigParameters().getSrsTimeBetweenSubmissionsInDays()) + .setTimeSinceOnboardingInHours(config.getAppConfigParameters().getSrsTimeSinceOnboardingInHours()) + .build(); + } + + private PPDDSelfReportSubmissionParametersAndroid buildPpddSelfReportSubmissionParametersAndroid( + final DistributionServiceConfig config) { + return PPDDSelfReportSubmissionParametersAndroid.newBuilder() + .setCommon(buildPpddSelfReportSubmissionParametersCommon(config)) + .setPpac(buildPpddPrivacyPreservingAccessControlParametersAndroid(config)) + .build(); + } + + private PPDDPrivacyPreservingAccessControlParametersAndroid buildPpddPrivacyPreservingAccessControlParametersAndroid( + final DistributionServiceConfig config) { + final AndroidSrsPpacParameters srs = config.getAppConfigParameters().getAndroidSrsPpacParameters(); + return PPDDPrivacyPreservingAccessControlParametersAndroid.newBuilder() + .setRequireBasicIntegrity(srs.getRequireBasicIntegrity()) + .setRequireCTSProfileMatch(srs.getRequireCtsProfileMatch()) + .setRequireEvaluationTypeBasic(srs.getRequireEvaluationTypeBasic()) + .setRequireEvaluationTypeHardwareBacked(srs.getRequireEvaluationTypeHardwareBacked()) .build(); } @@ -381,18 +419,18 @@ public ApplicationConfigurationIOS createIosV2Configuration(DistributionServiceC PreDistributionTrlValueMappingProvider trlValueMappingProvider) throws UnableToLoadFileException { - RiskCalculationParameters.Builder riskCalculationParameterBuilder = - YamlLoader.loadYamlIntoProtobufBuilder(V2_RISK_PARAMETERS_FILE, - RiskCalculationParameters.Builder.class); + RiskCalculationParameters.Builder riskCalculationParameterBuilder = loadYamlIntoProtobufBuilder( + V2_RISK_PARAMETERS_FILE, + RiskCalculationParameters.Builder.class); riskCalculationParameterBuilder.addAllTransmissionRiskValueMapping( trlValueMappingProvider.getTransmissionRiskValueMappingAsProto()); - CoronaTestParameters.Builder coronaTestParameters = - YamlLoader.loadYamlIntoProtobufBuilder(CORONA_TEST_PARAMETERS_FILE, - CoronaTestParameters.Builder.class); + CoronaTestParameters.Builder coronaTestParameters = loadYamlIntoProtobufBuilder( + CORONA_TEST_PARAMETERS_FILE, + CoronaTestParameters.Builder.class); - DeserializedExposureConfiguration exposureConfiguration = YamlLoader.loadYamlIntoClass( + DeserializedExposureConfiguration exposureConfiguration = loadYamlIntoClass( IOS_V2_EXPOSURE_CONFIGURATION_FILE, DeserializedExposureConfiguration.class); return ApplicationConfigurationIOS.newBuilder() @@ -412,6 +450,7 @@ public ApplicationConfigurationIOS createIosV2Configuration(DistributionServiceC .setCoronaTestParameters(coronaTestParameters) .setDgcParameters(buildDgcParameters(distributionServiceConfig, distributionServiceConfig .getAppConfigParameters().getDgcParameters().getIosDgcReissueServicePublicKeyDigest())) + .setSelfReportParameters(buildPpddSelfReportSubmissionParametersIos(distributionServiceConfig)) .build(); } @@ -489,8 +528,8 @@ private int getSemanticVersionNumber(String version, int position) { private ExposureDetectionParametersAndroid buildExposureDetectionParametersAndroid( DistributionServiceConfig distributionServiceConfig) { - AndroidExposureDetectionParameters androidExposureDetectionParameters = - distributionServiceConfig.getAppConfigParameters().getAndroidExposureDetectionParameters(); + AndroidExposureDetectionParameters androidExposureDetectionParameters = distributionServiceConfig + .getAppConfigParameters().getAndroidExposureDetectionParameters(); return ExposureDetectionParametersAndroid.newBuilder() .setMaxExposureDetectionsPerInterval(androidExposureDetectionParameters.getMaxExposureDetectionsPerInterval()) .setOverallTimeoutInSeconds(androidExposureDetectionParameters.getOverallTimeoutInSeconds()) @@ -499,34 +538,30 @@ private ExposureDetectionParametersAndroid buildExposureDetectionParametersAndro private ExposureDetectionParametersIOS buildExposureDetectionParametersIos( DistributionServiceConfig distributionServiceConfig) { - IosExposureDetectionParameters iosExposureDetectionParameters = - distributionServiceConfig.getAppConfigParameters().getIosExposureDetectionParameters(); + IosExposureDetectionParameters iosExposureDetectionParameters = distributionServiceConfig.getAppConfigParameters() + .getIosExposureDetectionParameters(); return ExposureDetectionParametersIOS.newBuilder() .setMaxExposureDetectionsPerInterval(iosExposureDetectionParameters.getMaxExposureDetectionsPerInterval()) .build(); } - - private List buildRevokedDayPackages( - List deserializedDayPackage) { - return deserializedDayPackage.stream().map(deserializedConfig -> - DayPackageMetadata.newBuilder() - .setRegion(deserializedConfig.getRegion()) - .setDate(deserializedConfig.getDate()) - .setEtag(deserializedConfig.getEtag()) - .build() - ).collect(Collectors.toList()); + private Collection buildRevokedDayPackages( + Collection deserializedDayPackage) { + return deserializedDayPackage.stream().map(deserializedConfig -> DayPackageMetadata.newBuilder() + .setRegion(deserializedConfig.getRegion()) + .setDate(deserializedConfig.getDate()) + .setEtag(deserializedConfig.getEtag()) + .build()).collect(Collectors.toList()); } - private List buildRevokedHourPackages( - List deserializedHourPackage) { - return deserializedHourPackage.stream().map(deserializedHourConfig -> - HourPackageMetadata.newBuilder() - .setRegion(deserializedHourConfig.getRegion()) - .setDate(deserializedHourConfig.getDate()) - .setHour(deserializedHourConfig.getHour()) - .setEtag(deserializedHourConfig.getEtag()) - .build()).collect(Collectors.toList()); + private Collection buildRevokedHourPackages( + Collection deserializedHourPackage) { + return deserializedHourPackage.stream().map(deserializedHourConfig -> HourPackageMetadata.newBuilder() + .setRegion(deserializedHourConfig.getRegion()) + .setDate(deserializedHourConfig.getDate()) + .setHour(deserializedHourConfig.getHour()) + .setEtag(deserializedHourConfig.getEtag()) + .build()).collect(Collectors.toList()); } private DGCParameters buildDgcParameters( @@ -551,13 +586,14 @@ private DGCParameters buildDgcParameters( .build(); } - private List buildBlockedUvciChunks( - List deserializedBlockedUvciChunks) { - return deserializedBlockedUvciChunks.stream().filter(dgcBlockedUvciChunk -> - TimeUtils.getNow().getEpochSecond() >= dgcBlockedUvciChunk.getValidFrom()) + private Collection buildBlockedUvciChunks( + Collection deserializedBlockedUvciChunks) { + return deserializedBlockedUvciChunks.stream() + .filter(dgcBlockedUvciChunk -> TimeUtils.getNow().getEpochSecond() >= dgcBlockedUvciChunk.getValidFrom()) .map(deserializedBlockedUvciChunk -> DGCBlockedUVCIChunk.newBuilder() .addAllIndices(deserializedBlockedUvciChunk.getIndices()) .setHash(ByteString.copyFrom(deserializedBlockedUvciChunk.getHash())) - .build()).collect(Collectors.toList()); + .build()) + .collect(Collectors.toList()); } } 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 b3b1b05ce5..8c6de4868e 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 @@ -1233,8 +1233,16 @@ public static class AppConfigParameters { private AndroidEventDrivenUserSurveyParameters androidEventDrivenUserSurveyParameters; private IosPrivacyPreservingAnalyticsParameters iosPrivacyPreservingAnalyticsParameters; private AndroidPrivacyPreservingAnalyticsParameters androidPrivacyPreservingAnalyticsParameters; + private AndroidSrsPpacParameters androidSrsPpacParameters; private DgcParameters dgcParameters; + @Min(1) + @Max(1000) + private int srsTimeSinceOnboardingInHours; + @Min(1) + @Max(1000) + private int srsTimeBetweenSubmissionsInDays; + public IosEventDrivenUserSurveyParameters getIosEventDrivenUserSurveyParameters() { return iosEventDrivenUserSurveyParameters; } @@ -1312,6 +1320,30 @@ public void setDgcParameters(DgcParameters dgcParameters) { this.dgcParameters = dgcParameters; } + public int getSrsTimeBetweenSubmissionsInDays() { + return srsTimeBetweenSubmissionsInDays; + } + + public int getSrsTimeSinceOnboardingInHours() { + return srsTimeSinceOnboardingInHours; + } + + public void setSrsTimeSinceOnboardingInHours(final int srsTimeSinceOnboardingInHours) { + this.srsTimeSinceOnboardingInHours = srsTimeSinceOnboardingInHours; + } + + public void setSrsTimeBetweenSubmissionsInDays(final int srsTimeBetweenSubmissionsInDays) { + this.srsTimeBetweenSubmissionsInDays = srsTimeBetweenSubmissionsInDays; + } + + public AndroidSrsPpacParameters getAndroidSrsPpacParameters() { + return androidSrsPpacParameters; + } + + public void setAndroidSrsPpacParameters(final AndroidSrsPpacParameters androidSrsPpacParameters) { + this.androidSrsPpacParameters = androidSrsPpacParameters; + } + public static class AndroidKeyDownloadParameters extends CommonKeyDownloadParameters { private static final int LOWER_BOUNDARY_DOWNLOAD_TIMEOUT = 0; @@ -1651,6 +1683,9 @@ public void setRequireEvaluationTypeHardwareBacked(Boolean requireEvaluationType } } + public static class AndroidSrsPpacParameters extends AndroidPrivacyPreservingAnalyticsParameters { + } + public static class DgcParameters { private DgcTestCertificateParameters dgcTestCertificateParameters; @@ -1783,6 +1818,8 @@ public void setValidFrom(Integer validFrom) { } } } + + } public static class DigitalGreenCertificate { diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index ed6f00719a..5ab3bef580 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -206,6 +206,13 @@ services: require-cts-profile-match: ${PPA_PPAC_REQUIRE_CTS_PROFILE_MATCH:false} require-evaluation-type-basic: ${PPA_PPAC_REQUIRE_EVALUATION_TYPE_BASIC:false} require-evaluation-type-hardware-backed: ${PPA_PPAC_REQUIRE_EVALUATION_TYPE_HARDWARE_BACKED:false} + srs-time-since-onboarding-in-hours: ${SRS_TIME_SINCE_ONBOARDING_IN_HOURS:48} + srs-time-between-submissions-in-days: ${SRS_TIME_BETWEEN_SUBMISSIONS_IN_DAYS:90} + android-srs-ppac-parameters: + require-basic-integrity: ${SRS_PPAC_REQUIRE_BASIC_INTEGRITY:false} + require-cts-profile-match: ${SRS_PPAC_REQUIRE_CTS_PROFILE_MATCH:false} + require-evaluation-type-basic: ${SRS_PPAC_REQUIRE_EVALUATION_TYPE_BASIC:false} + require-evaluation-type-hardware-backed: ${SRS_PPAC_REQUIRE_EVALUATION_TYPE_HARDWARE_BACKED:false} ios-qr-code-poster-template: published-archive-name: qr_code_poster_template_ios template: ${EVREG_QR_CODE_POSTER_TEMPLATE_IOS_FILE:} diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfigTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfigTest.java index 0bc1a1cdf3..f7d47e85b9 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfigTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/config/DistributionServiceConfigTest.java @@ -2,12 +2,15 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; import app.coronawarn.server.services.distribution.assembly.appconfig.ApplicationConfigurationPublicationConfig; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidExposureDetectionParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidKeyDownloadParameters; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.AndroidSrsPpacParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppConfigParameters.IosExposureDetectionParameters; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig.AppVersions; import java.util.List; @@ -235,6 +238,21 @@ void failsOnInvalidSupportedCountries() { } } + @Nested + class SrsParameterTest { + @Test + void androidSrsParameterTest() { + final AppConfigParameters app = distributionServiceConfig.getAppConfigParameters(); + assertEquals(48, app.getSrsTimeSinceOnboardingInHours()); + assertEquals(90, app.getSrsTimeBetweenSubmissionsInDays()); + AndroidSrsPpacParameters srs = app.getAndroidSrsPpacParameters(); + assertFalse(srs.getRequireBasicIntegrity()); + assertFalse(srs.getRequireCtsProfileMatch()); + assertFalse(srs.getRequireEvaluationTypeBasic()); + assertFalse(srs.getRequireEvaluationTypeHardwareBacked()); + } + } + /** * @MethodSource */ diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index fdb30bd58c..772759e0ac 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -163,7 +163,13 @@ services: require-cts-profile-match: false require-evaluation-type-basic: false require-evaluation-type-hardware-backed: false - + srs-time-since-onboarding-in-hours: 48 + srs-time-between-submissions-in-days: 90 + android-srs-ppac-parameters: + require-basic-integrity: false + require-cts-profile-match: false + require-evaluation-type-basic: false + require-evaluation-type-hardware-backed: false ios-qr-code-poster-template: published-archive-name: qr_code_poster_template_ios template: From 1674433c3cc530d5ab593b4987e1770a4240cb7d Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 24 Nov 2022 21:16:50 +0100 Subject: [PATCH 24/83] prepare maxSrsPerDay --- .../submission/controller/SubmissionController.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index a38d86234d..a25fb3f43d 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -3,6 +3,7 @@ import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING_VALUE; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_OTHER_VALUE; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST_VALUE; +import static java.time.ZoneOffset.UTC; import static org.springframework.util.ObjectUtils.isEmpty; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; @@ -27,6 +28,7 @@ import feign.FeignException; import feign.RetryableException; import io.micrometer.core.annotation.Timed; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -117,6 +119,12 @@ public DeferredResult> submitDiagnosisKey( if (!validSrsType(exposureKeys.getSubmissionType().getNumber())) { return badRequest(); } + int maxSrsPerDay = 42; // FIXME + if (diagnosisKeyService.countTodaysSrs() > maxSrsPerDay) { + logger.warn("We reached the maximum number ({}) of allowed Self-Report-Submissions for today ({})!", + maxSrsPerDay, LocalDate.now(UTC)); + return badRequest(); // FIXME + } return buildRealDeferredResult(exposureKeys, otp, srsOtpVerifier); } From 38c6bd2eeddce009cc5d23370512b33688aaf9db Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 25 Nov 2022 13:38:05 +0100 Subject: [PATCH 25/83] submissionServiceConfig MaxSrsPerDay --- .../config/SubmissionServiceConfig.java | 11 ++++ .../controller/SubmissionController.java | 13 ++-- .../src/main/resources/application.yaml | 2 + .../controller/HttpHeaderBuilder.java | 25 ++++---- .../controller/RequestExecutor.java | 62 +++++++++++-------- .../controller/SubmissionControllerTest.java | 23 ++++++- .../controller/SubmissionPayloadMockData.java | 11 ++++ .../src/test/resources/application.yaml | 1 + 8 files changed, 105 insertions(+), 43 deletions(-) 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 5653836835..f8ce650f16 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 @@ -60,6 +60,9 @@ public class SubmissionServiceConfig { @Min(1) @Max(144) private Integer minRollingPeriod; + @Min(1) + @Max(100000000) + private int maxSrsPerDay; /** * unencryptedCheckinsEnabled. @@ -226,6 +229,14 @@ public void setUnencryptedCheckinsEnabled(Boolean unencryptedCheckinsEnabled) { this.unencryptedCheckinsEnabled = unencryptedCheckinsEnabled; } + public int getMaxSrsPerDay() { + return maxSrsPerDay; + } + + public void setMaxSrsPerDay(final int maxSrsPerDay) { + this.maxSrsPerDay = maxSrsPerDay; + } + public static class Payload { @Min(7) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index a25fb3f43d..af1972c2b9 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -3,6 +3,7 @@ import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING_VALUE; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_OTHER_VALUE; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST_VALUE; +import static java.time.LocalDate.now; import static java.time.ZoneOffset.UTC; import static org.springframework.util.ObjectUtils.isEmpty; @@ -28,7 +29,6 @@ import feign.FeignException; import feign.RetryableException; import io.micrometer.core.annotation.Timed; -import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -119,11 +119,10 @@ public DeferredResult> submitDiagnosisKey( if (!validSrsType(exposureKeys.getSubmissionType().getNumber())) { return badRequest(); } - int maxSrsPerDay = 42; // FIXME - if (diagnosisKeyService.countTodaysSrs() > maxSrsPerDay) { + if (diagnosisKeyService.countTodaysSrs() >= submissionServiceConfig.getMaxSrsPerDay()) { logger.warn("We reached the maximum number ({}) of allowed Self-Report-Submissions for today ({})!", - maxSrsPerDay, LocalDate.now(UTC)); - return badRequest(); // FIXME + submissionServiceConfig.getMaxSrsPerDay(), now(UTC)); + return forbidden(); } return buildRealDeferredResult(exposureKeys, otp, srsOtpVerifier); } @@ -173,6 +172,10 @@ private DeferredResult> badRequest() { return new DeferredResult<>(null, () -> ResponseEntity.badRequest().build()); } + private DeferredResult> forbidden() { + return new DeferredResult<>(null, () -> ResponseEntity.status(HttpStatus.FORBIDDEN).build()); + } + /** * Handles "submission on behalf" requests. The basic idea is, that public health departments should be enabled to * warn all participants of a certain event although the department didn't join the event - it's like: "warn on behalf diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 78c42b754c..e5eb4d2095 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -43,6 +43,8 @@ services: min-rolling-period: ${MIN_ROLLING_PERIOD:1} # The flag for unencrypted checkins used by the EventCheckinFacade. unencrypted-checkins-enabled: ${UNENCRYPTED_CHECKINS_ENABLED:false} + # To prevent a potential overload of the distribution and the clients, we'll allow only this amount of self reports per day + max-srs-per-day: ${MAX_SRS_PER_DAY:1000000} payload: # The maximum number of keys accepted for any submission. max-number-of-keys: ${MAX_NUMBER_OF_KEYS:14} diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java index fe8bbbf9da..81d392103a 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java @@ -1,21 +1,23 @@ - - package app.coronawarn.server.services.submission.controller; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; +import static org.springframework.http.MediaType.valueOf; +import org.springframework.http.HttpHeaders; public class HttpHeaderBuilder { - private final HttpHeaders headers = new HttpHeaders(); - public static HttpHeaderBuilder builder() { return new HttpHeaderBuilder(); } + private final HttpHeaders headers = new HttpHeaders(); + + public HttpHeaders build() { + return headers; + } + public HttpHeaderBuilder contentTypeProtoBuf() { - headers.setContentType(MediaType.valueOf("application/x-protobuf")); + headers.setContentType(valueOf("application/x-protobuf")); return this; } @@ -24,6 +26,11 @@ public HttpHeaderBuilder cwaAuth() { return this; } + public HttpHeaderBuilder cwaOtp() { + headers.set("cwa-otp", "OTP ok"); + return this; + } + public HttpHeaderBuilder withCwaFake() { headers.set("cwa-fake", "1"); return this; @@ -33,8 +40,4 @@ public HttpHeaderBuilder withoutCwaFake() { headers.set("cwa-fake", "0"); return this; } - - public HttpHeaders build() { - return headers; - } } 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 af5621af88..4f9ad74d41 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 @@ -1,5 +1,3 @@ - - package app.coronawarn.server.services.submission.controller; import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; @@ -22,56 +20,70 @@ public class RequestExecutor { private static final URI SUBMISSION_URL = URI.create("/version/v1/diagnosis-keys"); - private static final URI SUBMISSION_ON_BEHALF_URL = URI.create("/version/v1/submission-on-behalf"); + private static final URI SUBMISSION_ON_BEHALF_URL = URI.create("/version/v1/submission-on-behalf"); private final TestRestTemplate testRestTemplate; - public RequestExecutor(TestRestTemplate testRestTemplate) { + public RequestExecutor(final TestRestTemplate testRestTemplate) { this.testRestTemplate = testRestTemplate; } - public ResponseEntity execute(HttpMethod method, RequestEntity requestEntity) { + private HttpHeaders buildDefaultHeader() { + return HttpHeaderBuilder.builder() + .contentTypeProtoBuf() + .cwaAuth() + .withoutCwaFake() + .build(); + } + + private HttpHeaders buildSrsHeader() { + return HttpHeaderBuilder.builder() + .contentTypeProtoBuf() + .cwaOtp() + .withoutCwaFake() + .build(); + } + + public ResponseEntity execute(final HttpMethod method, final RequestEntity requestEntity) { return testRestTemplate.exchange(SUBMISSION_URL, method, requestEntity, Void.class); } - public ResponseEntity executeSubmissionOnBehalf(HttpMethod method, RequestEntity requestEntity) { - return testRestTemplate.exchange(SUBMISSION_ON_BEHALF_URL, method, requestEntity, Void.class); + public ResponseEntity executePost(final Collection keys) { + return executePost(keys, buildDefaultHeader()); } - public ResponseEntity executePost(Collection keys, HttpHeaders headers) { - SubmissionPayload body = SubmissionPayload.newBuilder() + public ResponseEntity executePost(final Collection keys, final HttpHeaders headers) { + final SubmissionPayload body = SubmissionPayload.newBuilder() .setOrigin("DE") .addAllVisitedCountries(List.of("DE")) .addAllKeys(keys).build(); return executePost(body, headers); } - public ResponseEntity executePost(SubmissionPayload body, HttpHeaders headers) { - return execute(HttpMethod.POST, new RequestEntity<>(body, headers, HttpMethod.POST, SUBMISSION_URL)); + public ResponseEntity executePost(final SubmissionPayload body) { + return executePost(body, buildDefaultHeader()); } - public ResponseEntity executeSubmissionOnBehalf(SubmissionPayload body, HttpHeaders headers) { - return executeSubmissionOnBehalf(HttpMethod.POST, new RequestEntity<>(body, headers, HttpMethod.POST, SUBMISSION_ON_BEHALF_URL)); + public ResponseEntity executeSrsPost(final SubmissionPayload body) { + return executePost(body, buildSrsHeader()); } - public ResponseEntity executeSubmissionOnBehalf(SubmissionPayload body) { - return executeSubmissionOnBehalf(body, buildDefaultHeader()); + public ResponseEntity executePost(final SubmissionPayload body, final HttpHeaders headers) { + return execute(HttpMethod.POST, new RequestEntity<>(body, headers, HttpMethod.POST, SUBMISSION_URL)); } - public ResponseEntity executePost(SubmissionPayload body) { - return executePost(body, buildDefaultHeader()); + public ResponseEntity executeSubmissionOnBehalf(final HttpMethod method, + final RequestEntity requestEntity) { + return testRestTemplate.exchange(SUBMISSION_ON_BEHALF_URL, method, requestEntity, Void.class); } - public ResponseEntity executePost(Collection keys) { - return executePost(keys, buildDefaultHeader()); + public ResponseEntity executeSubmissionOnBehalf(final SubmissionPayload body) { + return executeSubmissionOnBehalf(body, buildDefaultHeader()); } - private HttpHeaders buildDefaultHeader() { - return HttpHeaderBuilder.builder() - .contentTypeProtoBuf() - .cwaAuth() - .withoutCwaFake() - .build(); + public ResponseEntity executeSubmissionOnBehalf(final SubmissionPayload body, final HttpHeaders headers) { + return executeSubmissionOnBehalf(HttpMethod.POST, + new RequestEntity<>(body, headers, HttpMethod.POST, SUBMISSION_ON_BEHALF_URL)); } } 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 4b6d6928d9..0f1f9a4c51 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 @@ -3,8 +3,8 @@ import static app.coronawarn.server.common.persistence.service.utils.checkins.CheckinsDateSpecification.TEN_MINUTE_INTERVAL_DERIVATION; 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.buildMultipleKeys; import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildKeyWithFutureInterval; +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; @@ -16,6 +16,7 @@ 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.buildPayloadWithoutOriginCountry; +import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildSrsPayload; import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildTemporaryExposureKey; import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.createRollingStartIntervalNumber; import static java.time.ZoneOffset.UTC; @@ -48,6 +49,7 @@ import app.coronawarn.server.services.submission.checkins.EventCheckinDataValidatorTest; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; import app.coronawarn.server.services.submission.monitoring.SubmissionMonitor; +import app.coronawarn.server.services.submission.verification.SrsOtpVerifier; import app.coronawarn.server.services.submission.verification.TanVerifier; import com.google.protobuf.ByteString; import java.time.Instant; @@ -95,10 +97,12 @@ class SubmissionControllerTest { @MockBean private FakeDelayManager fakeDelayManager; - @MockBean private TanVerifier tanVerifier; + @MockBean + private SrsOtpVerifier srsOtpVerifier; + @Autowired private RequestExecutor executor; @@ -113,6 +117,7 @@ class SubmissionControllerTest { public void setUpMocks() { traceTimeIntervalWarningRepository.deleteAll(); when(tanVerifier.verifyTan(anyString())).thenReturn(true); + when(srsOtpVerifier.verifyTan(anyString())).thenReturn(true); when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); } @@ -628,4 +633,18 @@ private static Stream invalidVisitedCountries() { private static Stream validVisitedCountries() { return Stream.of(Arguments.of(List.of("DE")), Arguments.of(List.of("DE", "FR"))); } + + /** + * @see SubmissionController#submitDiagnosisKey(SubmissionPayload, String, String) + */ + @Test + void testForbidTooManySrsPerDay() { + ResponseEntity response = executor + .executeSrsPost(buildSrsPayload(config, SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(OK); + when(diagnosisKeyService.countTodaysSrs()).thenReturn(config.getMaxSrsPerDay()); + response = executor + .executeSrsPost(buildSrsPayload(config, SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(FORBIDDEN); + } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java index 2b4706d24c..085557e7a2 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java @@ -8,6 +8,7 @@ import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey.Builder; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; +import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; import app.coronawarn.server.common.protocols.internal.pt.CheckIn; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; import com.google.protobuf.ByteString; @@ -167,6 +168,16 @@ public static SubmissionPayload buildPayloadWithVisitedCountries(List vi .build(); } + public static SubmissionPayload buildSrsPayload(final SubmissionServiceConfig config, final SubmissionType type) { + return SubmissionPayload.newBuilder() + .addAllKeys(buildMultipleKeys(config)) + .addAllVisitedCountries(List.of("DE")) + .setOrigin("DE") + .setRequestPadding(ByteString.copyFrom("PaddingString".getBytes())) + .setSubmissionType(type) + .build(); + } + @Deprecated public static SubmissionPayload buildPayloadWithCheckinData(List checkinData) { TemporaryExposureKey key = diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 21f208fb14..c2b079dda3 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -37,6 +37,7 @@ services: max-rolling-period: 144 min-rolling-period: 1 unencrypted-checkins-enabled: true + max-srs-per-day: 10 payload: max-number-of-keys: 100 supported-countries: DE,FR,IT From f1cd3a6df81d743acd896d31804cf08d5ffd196c Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 25 Nov 2022 14:32:51 +0100 Subject: [PATCH 26/83] pin sonar-maven-plugin version 3.9.1.2184 --- pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bfbddbd516..ce9b352e0a 100644 --- a/pom.xml +++ b/pom.xml @@ -472,7 +472,7 @@ maven-dependency-plugin - 3.1.2 + 3.3.0 maven-clean-plugin @@ -689,6 +689,11 @@ exec-maven-plugin 3.1.0 + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.9.1.2184 + From 3b6d92d7ff12b35dbc64aeb2532cf6b1bcd26749 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 1 Dec 2022 12:40:27 +0100 Subject: [PATCH 27/83] new certs --- docker-compose-test-secrets/certificate.crt | 20 ++++++++++-------- .../contains_efgs_truststore.jks | Bin 475 -> 507 bytes docker-compose-test-secrets/efgs.crt | 17 ++++++++------- docker-compose-test-secrets/efgs.p12 | Bin 926 -> 1104 bytes .../efgs_signing_cert.pem | 15 +++++++------ docker-compose-test-secrets/ssl.p12 | Bin 934 -> 1152 bytes scripts/generate_keys.sh | 14 ++++++------ 7 files changed, 35 insertions(+), 31 deletions(-) diff --git a/docker-compose-test-secrets/certificate.crt b/docker-compose-test-secrets/certificate.crt index 27c3eb2ac4..728014b44d 100644 --- a/docker-compose-test-secrets/certificate.crt +++ b/docker-compose-test-secrets/certificate.crt @@ -1,11 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIBojCCAUigAwIBAgIJANmLfKh4bEEUMAkGByqGSM49BAEwLzESMBAGA1UEAwwJ -bG9jYWxob3N0MRkwFwYDVQQLDBBDV0EtQmFja2VuZC1UZWFtMB4XDTIwMTIxNzEz -MjU0NFoXDTIxMTIxNzEzMjU0NFowLzESMBAGA1UEAwwJbG9jYWxob3N0MRkwFwYD -VQQLDBBDV0EtQmFja2VuZC1UZWFtMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -9/HUs+ssvOdmv+BZPjubaUiYOWYTd5iRMopbdBzpEPXbyQBSmOFesVJ7y3GTU/1q -l9FuIrqB7YBkhZZExPEqE6NOMEwwSgYDVR0RBEMwQYIJbG9jYWxob3N0ggpzdWJt -aXNzaW9uggZ1cGxvYWSCCGRvd25sb2FkgghjYWxsYmFja4IMZGlzdHJpYnV0aW9u -MAkGByqGSM49BAEDSQAwRgIhAJjrlFl4j9L6ULfFwpL8m3qe5GdNALCpYvR1lV5u -RyV1AiEAgF9+Q3caiAKXnGBnxF51eEIlOKBpWmFblC6JIus39Ag= +MIIB7zCCAZagAwIBAgIUWcSUQobZDpmbHWGWlf5Pm9Pc0aMwCgYIKoZIzj0EAwIw +LzESMBAGA1UEAwwJbG9jYWxob3N0MRkwFwYDVQQLDBBDV0EtQmFja2VuZC1UZWFt +MB4XDTIyMTEzMDE1MDY1NloXDTMyMTIwMTE1MDY1NlowLzESMBAGA1UEAwwJbG9j +YWxob3N0MRkwFwYDVQQLDBBDV0EtQmFja2VuZC1UZWFtMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAE9/HUs+ssvOdmv+BZPjubaUiYOWYTd5iRMopbdBzpEPXbyQBS +mOFesVJ7y3GTU/1ql9FuIrqB7YBkhZZExPEqE6OBjzCBjDBrBgNVHREEZDBiggls +b2NhbGhvc3SCCnN1Ym1pc3Npb26CBnVwbG9hZIIIZG93bmxvYWSCCGNhbGxiYWNr +ggxkaXN0cmlidXRpb26CFGhvc3QuZG9ja2VyLmludGVybmFsggllZmdzLWZha2Uw +HQYDVR0OBBYEFMiByCNtotATdzC45jeZZ7YhSopgMAoGCCqGSM49BAMCA0cAMEQC +IElURxTgk2l8KFXy2adODVUJxIwGK59Ykze6/Kw1aifFAiBRAySDEuJg3vVoru7D +syElXPvh4LtB+d5pIvnRN/xWFw== -----END CERTIFICATE----- diff --git a/docker-compose-test-secrets/contains_efgs_truststore.jks b/docker-compose-test-secrets/contains_efgs_truststore.jks index 5af31a924f88b9a01761e2b9fb2d2c613d9781ba..dc06d0c0d24403ff26505f0664369fdd328c0d07 100644 GIT binary patch delta 239 zcmVS^4Qoc)Q8*yN!FDxA3sBW-U_=om*FgOT3HX{f6S(9)M!!+9R-^y` delta 206 zcmey(e4BZKq;ajrIlGk%tPy&q29^vAjPned7-s>o{Q_ntMkXeaB8AW&MepvjYsm2} zH|bh;dtv2@_K7~~QiecaW@uz;WMmR0&TC|Z;7;rP_f;o|EP%~u(F{xJOheqHOznT2(8EH>~@^!m~Lnty*Y*R(E>lD6HE3q_6; z281#zFJ?S&DW19hU2fe5sp#KlkLCIbP82^Kac$*|i-8#@^mLE&E2w<3bT9+}U9n4P diff --git a/docker-compose-test-secrets/efgs.crt b/docker-compose-test-secrets/efgs.crt index 6335aa3151..7f934f5c20 100644 --- a/docker-compose-test-secrets/efgs.crt +++ b/docker-compose-test-secrets/efgs.crt @@ -1,11 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIBmjCCAT+gAwIBAgIUciBV+HLu3wcoHg6nNIqu26F5qIcwCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMTExNjEyNTIyNFoXDTIyMTExNjEy -NTIyNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D +MIIBujCCAWCgAwIBAgIUeXXAEdDiM+cbgkevS9swH988/mowCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMTEzMDE1MDY1NloXDTMyMTIwMTE1 +MDY1NlowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEosYj9mhDbr4jSuI3kWLYsr70Iq8FPGLBjeoW2SzBNo4NIjDSNalTLKiR -fyXCojmUJC+ZhZnQA7DXZADD/WNXXaNvMG0wawYDVR0RBGQwYoIJbG9jYWxob3N0 -ggpzdWJtaXNzaW9uggZ1cGxvYWSCCGRvd25sb2FkgghjYWxsYmFja4IMZGlzdHJp -YnV0aW9ughRob3N0LmRvY2tlci5pbnRlcm5hbIIJZWZncy1mYWtlMAoGCCqGSM49 -BAMCA0kAMEYCIQDR68iD1QGM/AD799cq1Myhfpw4sA+RSviL6w+/gwqWigIhANGG -u1mhFMRxUFUDI6MBwNJfA3/ubX6wGlv7zcZtTRGR +fyXCojmUJC+ZhZnQA7DXZADD/WNXXaOBjzCBjDBrBgNVHREEZDBigglsb2NhbGhv +c3SCCnN1Ym1pc3Npb26CBnVwbG9hZIIIZG93bmxvYWSCCGNhbGxiYWNrggxkaXN0 +cmlidXRpb26CFGhvc3QuZG9ja2VyLmludGVybmFsggllZmdzLWZha2UwHQYDVR0O +BBYEFJp0ut1l3OKF9LQiQa98XQFfUwpFMAoGCCqGSM49BAMCA0gAMEUCIFlLKfz5 +eS34hEBoKvo5msClDRdFf+P1BoP0qGAA+jxVAiEAr/FBUi7Jdl7LuOPFeA8O9mHQ +zw1rRtpROCDCwXY1Rws= -----END CERTIFICATE----- diff --git a/docker-compose-test-secrets/efgs.p12 b/docker-compose-test-secrets/efgs.p12 index ca9b9edae03d4574fad228dacce0b780a1ab2ac1..564e280b6833a83e3c1f2fe4accc7c4c9dd35bf3 100644 GIT binary patch literal 1104 zcmXqLV)0>OWHxAGVPfOdYV&CO&dbQoxS)yoGfNZmdxIwC*9J{Yix5(53!0cF1BLpT z7#V<62SSF?ARNwNw$;x0L%f^|| z=E0cC%)+R}A|RT+oUyUBCO+|X|GMgX58WG@m=s=1Sgg1hZMWg)MaeC0I@YJ_K8p71 z@tm8uOv~%7ZoQ$;H+NrwpZiYqZ&8TP(C=t7&>G>Ob3%bMR=S(ef`@*ebkkH=S z%<`=Dwd9vWI~3m48`-#u?s0iODKz-7?x%g_>1(u~%Dn#0Fe5=Lt=Dq-W`}q6n_EL} zUeM&NO3*nXra3eE|D>hIj;Wh}{5IKO64RaO0+UYa2VH0QKYhiO^=p@1k(gz7q`~Z2 z?Do%FzwMl3R&j5QS}yaU^E#!s818?o)UyYQ&E=6D-WK&*VQB}|6=p@6LPYhcS{3RJap6%DYdibWD zE{A*2@^1x4H%1z;-o4UvFLC9=I^WNCYo5(H$fvi9InJd$#E81pj{vSQ;lp zUHN#{sxHLxM7Co{x?X>1ANjTb7G zvs{#j^^v+ z0HjnAGK>Z?Y+O(ico^9X_*ghtmNlDpESb8NiHU=up^5Q_=cyUTJZ1>wADn9Yd*NG` zpZ}D?->ob^5A}5mgff8P440QM8(39@(Fs>92We)(z)X$-B>b9dT+(KgF7p)g>Q% zu|_1-@bXWuLmZb^7)VyWTl96?p7!a_7d>Fpk$vTrn8mH<68W$4+tr@uhxA(yzCC3B zcCBShF8|x6U%r3W2B#m9J>1UR%yaiNPsxpan>Rgga_nH-Bf&po@~-8}WE}L488k66 zAtjUrjbB(AKN>W?GiZFx#tlm{T#QT$8n+lUZbV5(%%F6{t7qe7rKo-zl#W;$Cq%Ek zdpLe>e`mkN;;Ty|CQH?=dgvi5=jt-tQ9<(J9URZK-kr=Ad8DB1s{QTXb%UH0nJ zcD;MsD3^3|tI{evEB-Lo;}K^{bgo?P*1N*-_x$P`8_LeyarpKl%Wj97p={mLGhf$t zvrC9gTJc)TJy^iv(%0ito^79+YO{RLmkZ%SClA`HK3#SC$;0f(HPaGR<9iKN4V2;D z;}kU%V-Z=WGxu=&>G>c1x{n?*Pchn;6Xca=U}&Idz{$p{&Bx3n#mc}UGGW%;C0Q@i ecVsb%pEq;As^DtW8qdNJ`!Psa`_*xCkY@oWDwVnb diff --git a/docker-compose-test-secrets/efgs_signing_cert.pem b/docker-compose-test-secrets/efgs_signing_cert.pem index a59cfdf731..f8362e54f2 100644 --- a/docker-compose-test-secrets/efgs_signing_cert.pem +++ b/docker-compose-test-secrets/efgs_signing_cert.pem @@ -1,9 +1,10 @@ -----BEGIN CERTIFICATE----- -MIIBQzCB6qADAgECAhRIrlL6f+SPcmD389ivpQyPIR2zJzAKBggqhkjOPQQDAjAU -MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjExMTE2MTI1MjI0WhcNMjIxMTE2MTI1 -MjI0WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB -BwNCAASixiP2aENuviNK4jeRYtiyvvQirwU8YsGN6hbZLME2jg0iMNI1qVMsqJF/ -JcKiOZQkL5mFmdADsNdkAMP9Y1ddoxowGDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF -4DAKBggqhkjOPQQDAgNIADBFAiArXtLCJSH8mk5HiTRIgUqEzyC4enVvlnj3rCnV -0ZOL/QIhAPeEjVikcKtGc2eBXzug4ouQP2Isahskte6x4i8gfAFU +MIIBZDCCAQmgAwIBAgIULsjaPGkykIlLantbxxX8TquzQnkwCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMTEzMDE1MDY1NloXDTMyMTIwMTE1 +MDY1NlowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEosYj9mhDbr4jSuI3kWLYsr70Iq8FPGLBjeoW2SzBNo4NIjDSNalTLKiR +fyXCojmUJC+ZhZnQA7DXZADD/WNXXaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMC +BeAwHQYDVR0OBBYEFJp0ut1l3OKF9LQiQa98XQFfUwpFMAoGCCqGSM49BAMCA0kA +MEYCIQDG9M+7Igov9zde0S5jlq/tbDcyncbvkIs7uoOC0/ZnawIhAMYv5cT1hKUR ++01vDOpU5Z5LqL7PjZWDdZViEeyYWjnz -----END CERTIFICATE----- diff --git a/docker-compose-test-secrets/ssl.p12 b/docker-compose-test-secrets/ssl.p12 index 128402cbe7b58846aab6574627371e54cd960d90..ef586d543e1f927307d2cfcd73a4bdbb26bb41b8 100644 GIT binary patch literal 1152 zcmXqLVyR(bWHxAGF=FG?YV&CO&dbQoxS)wenWc$E9w;nr(8P2JA;q?!iRmyx>$@UU?}Y-8eNWiXIs<4kDt zU`%CZVbo#~Ft=YBEGo#uAi1wEU0$GH|WXD_VDh~9nXrjzrZTN+7* z^Z$qZaSV&me|coT?8aQhcyO)vV*xr?&^!C2pOtqxbx_uxlIh zl!_$0_Z*yRu(~OpQ|3UAgyi-EqKSH;>laCHGC9=sd`DKv;)6~z7v2tYRPE?UN}s;! z!KNb3URkqqSB&P#@4Pg_ct&EY1e3y+ssm{XD)Q}4!k=EHDg8RGVKetjOnipWTS>yg4u}#hI>|L5>!e(7c@_`hF( zLu*q+)1}Up(+<8;?R!0Sr^uJG7ZESa*pJ8DTr1gfztY~}>K}*4P7{9T@8^iUBONvA zlJJ((2RQT6cEx%e+i=jhK%0Nt)2#Ju+q5&JTiO&EekWu|DV^M~yw2fJoP+SwWe@b$ z%!>9Fc9jw9-?diFe%7V0MnZQ*Of)4cvwwU!QC(%}vy+b@sX8{)zqNRsf#J-Vw@%3a zIw@dUt0=#l^I*UG_WVCV)3b|qWvom8_iE#uY5NSC7#-p16O`Z?jaZr(^?<@!K&-~b z4b2NYT#QT$8s8fx)y4G zn=vuLlS@Ft;Pt|XmfIZG2zlT7HMf54m0F_)&g1u2|LEQl6*6s}V4T<7>TI3TsBC_o z?3H($7HpkqRe5vLt8+5h5iBP^tFnbBx*FI25WTqezWd$pFS9QnT4d*=V1E7c%kNpd z+iOK8tF7Y>u{_}^lz4L1{)>rM&b<}cD&gNWuiIj-zO2}@rJj8E?2P6csv0Q6Lx)q; zP>e-n&G9JvzO()Za*xbw(C1$qY36W6)xgof(0~`7cbOPj8CVp=6(ZmB99>qcs~uu& m|JHf;nmUn*U7FKw2It*!mb%${f`y}R*591&`NoQ%kOBaog5NLz literal 934 zcmXqLVqV0=$ZXKWoWaJa)#lOmotKfFaX}MvBuf)>C{Q@ipoz&HA;q?!iOB*eWWvPA z0HkygGK>Z?Y+O(ico^9X_*giUL+i>C-#>ZH#KghS(8R=WiKRtt;j%i18%kRD-pben ze)z6n=K632`xU3Qc{N)!;}5_3Jn{3VX_MQH!nQ;lf2^Ld_;f(7|C=z*$U~NY#W;TM z3Y{z#xc1*Z%aCZfqPbU=YU(o8B`&;GC(bkb!3qH|Ldj#zgZnGg_Nv=Bm&N?tBV#%_fn}b=f>m?gCpAa1 zJ#zZFZm~nbg%2C**By`FwnFHZ$Z~@T!As}0T$fT`_e5@T&b`jB)lVNjxZ8du@Mudh zgZs5FQUR81pO)?llG$D3?-MO~d5KdkAJ>yyqL|v`G;AU&zbi#ztDV*$(4C> zhgV1Da!V+mG5)#w!qZ)4zS}LaWMw>%>V5NzXR_sEfeuBcDtEnCNz;xW z5Z(0cT6yl#J;$%SzV_=Oqty-DPy-&Gua^#1if*)gDfjmyOUN^px2BSFR@BDNTAl3^ z!ewK-db?G{LRHxq{ayL#)z5Yx6>gq$ZpVbp&!30!=Et#zvK-$(z4y7vU3Z&5aRuEU zzZT7YaE;>{!-4sKp1hkK_hrkcl~Q*<2wI*qwY<`{rSYF@=L4OilOLs*Y-rP0H?6D6 zl{po4-=K++2`RxWX#B#`_|c&8ok8PkHf~tb;bLT3(745*aU)84Vg{wB<-(GSJT`Bv z0Hr6E#tA)+F)KtKU(b3ewz{#(Z&B`)vzl}Mh)-PkMYu21>+9mJ-pvxRfA2qNFWb={ zH-jx>t!3WiMjPuJkD?^lg|2yoPm4HFbN|+cp#SUG884;GQRxlz{!Gb zPxKh`4%xo<_OH=LPAi^z#@;kL7k-}6eB%kOazT#$QFcZ-REp@E_SCmX9aA2X8_D+7zj m4Uq@wu^-sA8`~q&4)_KoH{DYUW#KsfjJ0Ai Date: Thu, 1 Dec 2022 14:51:19 +0100 Subject: [PATCH 28/83] SrsVerifyClient --- .../config/SubmissionServiceConfig.java | 19 ++- .../SrsVerifyServiceHealthIndicator.java | 37 ++++++ .../services/submission/verification/Otp.java | 62 +++++++++ .../verification/SrsOtpVerifier.java | 23 ++-- .../verification/SrsVerifyClient.java | 26 ++++ .../src/main/resources/application.yaml | 3 + .../verification/SrsVerifierTest.java | 118 ++++++++++++++++++ .../src/test/resources/application.yaml | 3 + 8 files changed, 280 insertions(+), 11 deletions(-) create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/verification/Otp.java create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java create mode 100644 services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java 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 f8ce650f16..de49b7dec8 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 @@ -52,6 +52,7 @@ public class SubmissionServiceConfig { private DataSize maximumRequestSize; private Payload payload; private Verification verification; + private Verification srsVerify; private Monitoring monitoring; private Client client; @Min(1) @@ -306,7 +307,7 @@ public String getVerificationPath() { return verification.getPath(); } - private static class Verification { + static class Verification { @Pattern(regexp = URL_WITH_PORT_REGEX) private String baseUrl; @@ -370,6 +371,22 @@ public void setClient(Client client) { this.client = client; } + Verification getSrsVerify() { + return srsVerify; + } + + void setSrsVerify(final Verification srsVerify) { + this.srsVerify = srsVerify; + } + + public String getSrsVerifyBaseUrl() { + return getSrsVerify().getBaseUrl(); + } + + public String getSrsVerifyPath() { + return getSrsVerify().getPath(); + } + public static class Client { private Ssl ssl; diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java new file mode 100644 index 0000000000..e69cbaebc4 --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java @@ -0,0 +1,37 @@ +package app.coronawarn.server.services.submission.monitoring; + +import app.coronawarn.server.services.submission.verification.Otp; +import app.coronawarn.server.services.submission.verification.SrsVerifyClient; +import app.coronawarn.server.services.submission.verification.Tan; +import feign.FeignException; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +/** + * Health indicator exposed in the readiness probe of the application. Fires NULL UUID tan to the verification service, + * and checks that the response code is 2xx or 404, else sets health to down, and marks application as not ready for + * requests. + */ +@Component("srsVerifyService") +public class SrsVerifyServiceHealthIndicator implements HealthIndicator { + + private final SrsVerifyClient verificationServerClient; + + SrsVerifyServiceHealthIndicator(final SrsVerifyClient verificationServerClient) { + this.verificationServerClient = verificationServerClient; + } + + @Override + public Health health() { + try { + verificationServerClient.verifyOtp(Otp.of(Tan.of("00000000-0000-0000-0000-000000000000"))); + } catch (final FeignException.NotFound e) { + // expected + } catch (final Exception e) { + // http status code is neither 2xx nor 404 + return Health.down().build(); + } + return Health.up().build(); + } +} diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/Otp.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/Otp.java new file mode 100644 index 0000000000..e7b1025bbb --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/Otp.java @@ -0,0 +1,62 @@ +package app.coronawarn.server.services.submission.verification; + +import java.util.Objects; +import java.util.UUID; + +/** + * A representation of a One-Time-Passcode (OTP). + */ +public class Otp { + /** + * Creates a new {@link #Otp} instance for the given tan string. + * + * @param tan A valid UUID string representation. + * @return The Otp instance + * @throws IllegalArgumentException when the given tan string is not a valid UUID. + */ + public static Otp of(final Tan tan) { + return new Otp(tan.getTan()); + } + + private final UUID otp; + + private Otp(final UUID otp) { + this.otp = otp; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final Otp other = (Otp) o; + return otp.equals(other.otp); + } + + /** + * Returns the OTP entity as UUID. + * + * @return the {@link #otp}. + */ + public UUID getOtp() { + return otp; + } + + @Override + public int hashCode() { + return Objects.hash(otp); + } + + /** + * Returns the OTP in it's string representation. + * + * @return the tan UUID as a string. + */ + @Override + public String toString() { + return otp.toString(); + } +} diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java index 2169a89b5a..195a2f66b4 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java @@ -3,6 +3,7 @@ import feign.FeignException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; /** @@ -13,22 +14,24 @@ public class SrsOtpVerifier extends TanVerificationService { private static final Logger logger = LoggerFactory.getLogger(SrsOtpVerifier.class); - public SrsOtpVerifier(VerificationServerClient verificationServerClient) { - super(verificationServerClient); + private final SrsVerifyClient client; + + public SrsOtpVerifier(final SrsVerifyClient client) { + super(null); + this.client = client; } - boolean verifyWithVerificationService(Tan tan) { + @Override + boolean verifyWithVerificationService(final Tan tan) { try { - logger.info("Calling SRS OPT verification Service ..."); + logger.debug("Calling SRS OPT verification Service ..."); - // FIXME! - // ResponseEntity result = verificationServerClient.verifyTan(tan); - // List typeHeaders = result.getHeaders().getOrEmpty(CWA_TELETAN_TYPE_RESPONSE_HEADER); - // logger.info("Received response from Verification Service: {}", result); + final ResponseEntity result = client.verifyOtp(Otp.of(tan)); + logger.debug("Received response from SRS-verify: {}", result); return true; - } catch (FeignException.NotFound e) { - logger.info("SRS OTP verification service reported: NotFound", e); + } catch (final FeignException.NotFound e) { + logger.warn("SRS OTP verification service reported: NotFound", e); return false; } } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java new file mode 100644 index 0000000000..da9f35f412 --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java @@ -0,0 +1,26 @@ +package app.coronawarn.server.services.submission.verification; + +import io.micrometer.core.annotation.Timed; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; + +/** + * This is a Spring Cloud Feign based HTTP client that allows type-safe HTTP calls and abstract the implementation away. + */ +@FeignClient(name = "srs-verify", + configuration = VerificationServerClientConfiguration.class, + url = "${services.submission.srs-verify.base-url}") +public interface SrsVerifyClient { + + /** + * This methods calls the srs-verify service with the given {@link Otp}. + * + * @param otp the {@link Otp} to verify. + * @return 404 when the tan is not valid. + */ + @Timed + @PostMapping(value = "${services.submission.srs-verify.path}", consumes = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity verifyOtp(final Otp otp); +} diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index e5eb4d2095..7b2707031a 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -60,6 +60,9 @@ services: verification: base-url: ${VERIFICATION_BASE_URL:http://localhost:8004} path: /version/v1/tan/verify + srs-verify: + base-url: ${SRS_VERIFY_BASE_URL:https://localhost:8105} + path: /version/v1/srs monitoring: # The batch size (number of requests) to use for monitoring request count. batch-size: 5 diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java new file mode 100644 index 0000000000..6fa1f072e5 --- /dev/null +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java @@ -0,0 +1,118 @@ +package app.coronawarn.server.services.submission.verification; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.Assert.assertEquals; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; + +import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; +import com.github.tomakehurst.wiremock.WireMockServer; +import feign.FeignException; +import java.util.UUID; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; + +@SpringBootTest +@DirtiesContext +class SrsVerifierTest { + + private static WireMockServer server; + + @BeforeAll + static void setupWireMock() { + server = new WireMockServer(options().port(1234)); // test/resources/application.yaml + server.start(); + } + + @AfterAll + static void tearDown() { + server.stop(); + } + + static final String TOKEN = "otp"; + + private String path; + + private String randomUuid; + + @Autowired + private SubmissionServiceConfig submissionServiceConfig; + + @MockBean + TestRestTemplate testRestTemplate; + + @Autowired + private SrsOtpVerifier verifier; + + @BeforeEach + void setup() { + path = submissionServiceConfig.getSrsVerifyPath(); + assertEquals("/version/v1/srs", path); + randomUuid = UUID.randomUUID().toString(); + server.resetAll(); + } + + @Test + void checkInternalServerError() { + server.stubFor( + post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()))); + + assertThatExceptionOfType(FeignException.class).isThrownBy(() -> verifier.verifyTan(randomUuid)); + } + + @Test + void checkInvalidOtp() { + server.stubFor( + post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + + assertThat(verifier.verifyTan(randomUuid)).isFalse(); + } + + @Test + void checkTimeout() { + server.stubFor( + post(urlEqualTo(path)) + .withRequestBody(matchingJsonPath(TOKEN, equalTo(randomUuid))) + .withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(HttpStatus.OK.value()).withFixedDelay(1000))); + + assertThatExceptionOfType(FeignException.class).isThrownBy(() -> verifier.verifyTan(randomUuid)); + } + + @Test + void checkTooLongOtp() { + server.stubFor( + post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + + assertThat(verifier.verifyTan(randomUuid + randomUuid)).isFalse(); + } + + @Test + void checkValidOtp() { + server.stubFor( + post(urlEqualTo(path)) + .withRequestBody(matchingJsonPath(TOKEN, equalTo(randomUuid))) + .withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); + + assertThat(verifier.verifyTan(randomUuid)).isTrue(); + } +} diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index c2b079dda3..2e8daebae3 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -47,6 +47,9 @@ services: verification: base-url: http://localhost:1234 path: /version/v1/tan/verify + srs-verify: + base-url: http://localhost:1234 + path: /version/v1/srs monitoring: batch-size: 5 client: From 4102b8fec20793220820795edebec8eba7a3e956 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 10:24:25 +0100 Subject: [PATCH 29/83] actuator heath test incl. srs-verify --- .../SrsVerifyServiceHealthIndicator.java | 10 +- .../VerificationServiceHealthIndicator.java | 6 +- .../src/main/resources/application.yaml | 2 +- .../monitoring/HealthIndicatorTest.java | 92 +++++++++++++++++++ ...erificationServiceHealthIndicatorTest.java | 75 --------------- .../src/test/resources/application.yaml | 2 +- 6 files changed, 100 insertions(+), 87 deletions(-) create mode 100644 services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/HealthIndicatorTest.java delete mode 100644 services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicatorTest.java diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java index e69cbaebc4..f0790b6910 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java @@ -16,21 +16,21 @@ @Component("srsVerifyService") public class SrsVerifyServiceHealthIndicator implements HealthIndicator { - private final SrsVerifyClient verificationServerClient; + private final SrsVerifyClient client; - SrsVerifyServiceHealthIndicator(final SrsVerifyClient verificationServerClient) { - this.verificationServerClient = verificationServerClient; + SrsVerifyServiceHealthIndicator(final SrsVerifyClient client) { + this.client = client; } @Override public Health health() { try { - verificationServerClient.verifyOtp(Otp.of(Tan.of("00000000-0000-0000-0000-000000000000"))); + client.verifyOtp(Otp.of(Tan.of("00000000-0000-0000-0000-000000000000"))); } catch (final FeignException.NotFound e) { // expected } catch (final Exception e) { // http status code is neither 2xx nor 404 - return Health.down().build(); + return Health.down(e).build(); } return Health.up().build(); } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java index 78b048cb36..e2155e22a7 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java @@ -1,5 +1,3 @@ - - package app.coronawarn.server.services.submission.monitoring; import app.coronawarn.server.services.submission.verification.Tan; @@ -30,12 +28,10 @@ public Health health() { verificationServerClient.verifyTan(Tan.of("00000000-0000-0000-0000-000000000000")); } catch (FeignException.NotFound e) { // expected - return Health.up().build(); } catch (Exception e) { // http status code is neither 2xx nor 404 - return Health.down().build(); + return Health.down(e).build(); } return Health.up().build(); } - } diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 7b2707031a..8f945204dd 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -105,7 +105,7 @@ management: health: group: readiness: - include: db, verificationService + include: db, verificationService, srsVerifyService show-details: always endpoints: web: diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/HealthIndicatorTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/HealthIndicatorTest.java new file mode 100644 index 0000000000..500730d6ca --- /dev/null +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/HealthIndicatorTest.java @@ -0,0 +1,92 @@ + +package app.coronawarn.server.services.submission.monitoring; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import app.coronawarn.server.services.submission.verification.SrsVerifyClient; +import app.coronawarn.server.services.submission.verification.VerificationServerClient; +import feign.FeignException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@TestPropertySource(properties = { "management.port=" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DirtiesContext +class HealthIndicatorTest { + + @MockBean + private VerificationServerClient verificationClient; + + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @MockBean + private SrsVerifyClient srsClient; + + @BeforeEach + public void beforeEach() { + mvc = MockMvcBuilders.webAppContextSetup(context).build(); + } + + @Test + void checkIsAliveEvenIfVerificationServerIsDown() throws Exception { + when(verificationClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); + when(srsClient.verifyOtp(any())).thenThrow(FeignException.InternalServerError.class); + mvc.perform(get("/actuator/health/liveness")).andExpect(status().is2xxSuccessful()).andReturn(); + } + + @Test + void checkIsHealthyIfVerificationServerIsRunning() throws Exception { + when(verificationClient.verifyTan(any())).thenReturn(null); + when(srsClient.verifyOtp(any())).thenReturn(null); + mvc.perform(get("/actuator/health")).andExpect(status().is2xxSuccessful()).andReturn(); + } + + @Test + void checkIsHealthyIfVerificationServerIsRunningAndExceptionIsThrown() throws Exception { + when(verificationClient.verifyTan(any())).thenThrow(FeignException.NotFound.class); + when(srsClient.verifyOtp(any())).thenThrow(FeignException.NotFound.class); + mvc.perform(get("/actuator/health")).andExpect(status().is2xxSuccessful()).andReturn(); + } + + @Test + void checkIsNotReadyIfVerificationServerIsDown() throws Exception { + when(verificationClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); + when(srsClient.verifyOtp(any())).thenThrow(FeignException.InternalServerError.class); + mvc.perform(get("/actuator/health/readiness")).andExpect(status().isServiceUnavailable()).andReturn(); + } + + @Test + void checkIsUnhealthyIfOneServerIsDown() throws Exception { + when(verificationClient.verifyTan(any())).thenReturn(null); + when(srsClient.verifyOtp(any())).thenThrow(FeignException.InternalServerError.class); + mvc.perform(get("/actuator/health")).andExpect(status().isServiceUnavailable()).andReturn(); + } + + @Test + void checkIsUnhealthyIfOtherServerIsDown() throws Exception { + when(verificationClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); + when(srsClient.verifyOtp(any())).thenReturn(null); + mvc.perform(get("/actuator/health")).andExpect(status().isServiceUnavailable()).andReturn(); + } + + @Test + void checkIsUnhealthyIfVerificationServerIsDown() throws Exception { + when(verificationClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); + when(srsClient.verifyOtp(any())).thenThrow(FeignException.InternalServerError.class); + mvc.perform(get("/actuator/health")).andExpect(status().isServiceUnavailable()).andReturn(); + } +} diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicatorTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicatorTest.java deleted file mode 100644 index 9966ffe554..0000000000 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicatorTest.java +++ /dev/null @@ -1,75 +0,0 @@ - - -package app.coronawarn.server.services.submission.monitoring; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import app.coronawarn.server.services.submission.verification.VerificationServerClient; -import feign.FeignException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -@TestPropertySource(properties = {"management.port="}) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DirtiesContext -class VerificationServiceHealthIndicatorTest { - - @MockBean - private VerificationServerClient verificationServerClient; - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @BeforeEach - public void setup() { - mvc = MockMvcBuilders.webAppContextSetup(context).build(); - } - - @Test - void checkIsHealthyIfVerificationServerIsRunningAndExceptionIsThrown() throws Exception { - when(verificationServerClient.verifyTan(any())).thenThrow(FeignException.NotFound.class); - mvc.perform(get("/actuator/health")) - .andExpect(status().is2xxSuccessful()).andReturn(); - } - - @Test - void checkIsHealthyIfVerificationServerIsRunning() throws Exception { - when(verificationServerClient.verifyTan(any())).thenReturn(null); - mvc.perform(get("/actuator/health")) - .andExpect(status().is2xxSuccessful()).andReturn(); - } - - @Test - void checkIsUnhealthyIfVerificationServerIsDown() throws Exception { - when(verificationServerClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); - mvc.perform(get("/actuator/health")) - .andExpect(status().isServiceUnavailable()).andReturn(); - } - - @Test - void checkIsNotReadyIfVerificationServerIsDown() throws Exception { - when(verificationServerClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); - mvc.perform(get("/actuator/health/readiness")) - .andExpect(status().isServiceUnavailable()).andReturn(); - } - - @Test - void checkIsAliveEvenIfVerificationServerIsDown() throws Exception { - when(verificationServerClient.verifyTan(any())).thenThrow(FeignException.InternalServerError.class); - mvc.perform(get("/actuator/health/liveness")) - .andExpect(status().is2xxSuccessful()).andReturn(); - } -} diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 2e8daebae3..5c571d70f5 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -65,7 +65,7 @@ management: health: group: readiness: - include: db, verificationService + include: db, verificationService, srsVerifyService endpoints: web: exposure: From dc3d91a1b97bf50b6f9dc940f4ea03f726762d1e Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 11:44:32 +0100 Subject: [PATCH 30/83] use Collection in APIs; fix code smell: 'Stream.toList()' --- .../service/DiagnosisKeyService.java | 12 +++++------ .../common/ValidDiagnosisKeyFilter.java | 20 +++++-------------- ...agnosisKeyServiceMockedRepositoryTest.java | 10 ++++------ .../DiagnosisKeyServiceTestHelper.java | 17 +++++++--------- .../SubmissionServiceTrlMappingTest.java | 7 +++---- 5 files changed, 24 insertions(+), 42 deletions(-) diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java index 2a7f199838..947d18c218 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java @@ -13,8 +13,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Sort; @@ -88,9 +86,9 @@ public void applySrsRetentionPolicy(final int retentionDays) { * * @return ValidDiagnosisKeyFilter */ - public List getDiagnosisKeys() { - final List diagnosisKeys = createStreamFromIterator( - keyRepository.findAll(Sort.by(Direction.ASC, "submissionTimestamp")).iterator()).collect(Collectors.toList()); + public Collection getDiagnosisKeys() { + final Collection diagnosisKeys = createStreamFromIterator( + keyRepository.findAll(Sort.by(Direction.ASC, "submissionTimestamp")).iterator()).toList(); return validationFilter.filter(diagnosisKeys); } @@ -101,8 +99,8 @@ public List getDiagnosisKeys() { * @param daysToFetch time in days, that should be published * @return List of {@link DiagnosisKey}s filtered by {@link #validationFilter}. */ - public List getDiagnosisKeysWithMinTrl(final int minTrl, final int daysToFetch) { - final List diagnosisKeys = keyRepository.findAllWithTrlGreaterThanOrEqual(minTrl, + public Collection getDiagnosisKeysWithMinTrl(final int minTrl, final int daysToFetch) { + final Collection diagnosisKeys = keyRepository.findAllWithTrlGreaterThanOrEqual(minTrl, daysToSeconds(daysToFetch)); return validationFilter.filter(diagnosisKeys); } diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/common/ValidDiagnosisKeyFilter.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/common/ValidDiagnosisKeyFilter.java index ebeaa9de6e..50309dd933 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/common/ValidDiagnosisKeyFilter.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/common/ValidDiagnosisKeyFilter.java @@ -1,12 +1,7 @@ - - package app.coronawarn.server.common.persistence.service.common; - import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; import javax.validation.ConstraintViolation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,15 +13,13 @@ public class ValidDiagnosisKeyFilter { private static final Logger logger = LoggerFactory.getLogger(ValidDiagnosisKeyFilter.class); /** - * Rerturns a subset of diagnosis keys from the given list which have - * passed the default entity validation. + * Returns a subset of diagnosis keys from the given list which have passed the default entity validation. * * @param diagnosisKeys list of DiagnosisKey * @return list of valid DiagnosisKey */ - public List filter(List diagnosisKeys) { - List validDiagnosisKeys = - diagnosisKeys.stream().filter(this::isDiagnosisKeyValid).collect(Collectors.toList()); + public Collection filter(final Collection diagnosisKeys) { + Collection validDiagnosisKeys = diagnosisKeys.stream().filter(this::isDiagnosisKeyValid).toList(); int numberOfDiscardedKeys = diagnosisKeys.size() - validDiagnosisKeys.size(); logger.info("Retrieved {} diagnosis key(s). Discarded {} diagnosis key(s) from the result as invalid.", @@ -42,16 +35,13 @@ public List filter(List diagnosisKeys) { * @return boolean value to indicate if the DiagnosisKey is valid */ public boolean isDiagnosisKeyValid(DiagnosisKey diagnosisKey) { - Collection> violations = diagnosisKey.validate(); + final Collection> violations = diagnosisKey.validate(); boolean isValid = violations.isEmpty(); if (!isValid) { - List violationMessages = - violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList()); + final Collection violationMessages = violations.stream().map(ConstraintViolation::getMessage).toList(); logger.warn("Validation failed for diagnosis key from database. Violations: {}", violationMessages); } - return isValid; } - } 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 b67cf6776e..d96e3b629b 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 @@ -1,5 +1,3 @@ - - package app.coronawarn.server.common.persistence.domain; import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.assertDiagnosisKeysEqual; @@ -12,7 +10,7 @@ import app.coronawarn.server.common.protocols.external.exposurenotification.ReportType; import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; import java.nio.charset.StandardCharsets; -import java.util.List; +import java.util.Collection; import java.util.Set; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -46,7 +44,7 @@ void testKeyRetrievalWithInvalidDbEntries() { mockInvalidKeyInDb(list(invalidKey1, invalidKey2)); - List actualKeys = diagnosisKeyService.getDiagnosisKeys(); + Collection actualKeys = diagnosisKeyService.getDiagnosisKeys(); assertThat(actualKeys).isEmpty(); } @@ -62,13 +60,13 @@ void testKeyRetrievalWithInvalidAndValidDbEntries() { mockInvalidKeyInDb(expKeys); - List actualKeys = diagnosisKeyService.getDiagnosisKeys(); + Collection actualKeys = diagnosisKeyService.getDiagnosisKeys(); expKeys.remove(invalidKey1); expKeys.remove(invalidKey2); assertDiagnosisKeysEqual(expKeys, actualKeys); } - private void mockInvalidKeyInDb(List keys) { + private void mockInvalidKeyInDb(final Collection keys) { when(diagnosisKeyRepository.findAll(Sort.by(Direction.ASC, "submissionTimestamp"))).thenReturn(keys); } diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTestHelper.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTestHelper.java index 58cc3d197d..9578c080b3 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTestHelper.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTestHelper.java @@ -1,5 +1,3 @@ - - package app.coronawarn.server.common.persistence.service; import static app.coronawarn.server.common.persistence.domain.DiagnosisKey.ROLLING_PERIOD_MINUTES_INTERVAL; @@ -16,7 +14,8 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; -import java.util.List; +import java.util.Collection; +import java.util.Iterator; import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -25,15 +24,13 @@ public class DiagnosisKeyServiceTestHelper { private static final Random random = new Random(); - public static void assertDiagnosisKeysEqual(List expKeys, - List actKeys) { + public static void assertDiagnosisKeysEqual(Collection expKeys, Collection actKeys) { assertThat(actKeys).withFailMessage("Cardinality mismatch").hasSameSizeAs(expKeys); - for (int i = 0; i < expKeys.size(); i++) { - var expKey = expKeys.get(i); - var actKey = actKeys.get(i); - - assertDiagnosisKeysEqual(expKey, actKey); + final Iterator expIt = expKeys.iterator(); + final Iterator actIt = actKeys.iterator(); + for (; expIt.hasNext() && actIt.hasNext();) { + assertDiagnosisKeysEqual(expIt.next(), actIt.next()); } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java index 4ebe83c5da..0799747930 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java @@ -12,6 +12,7 @@ import app.coronawarn.server.common.persistence.service.DiagnosisKeyService; import app.coronawarn.server.services.submission.controller.RequestExecutor; import app.coronawarn.server.services.submission.verification.TanVerifier; +import java.util.Collection; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -54,10 +55,10 @@ void checkResponseStatusForTrlMappingLoadingCorrectly() { void checkTrlMappingDerivation() { // buildMultipleKeys(config) creates keys with TRL 3, 6, 8. // The mappings for these values can be found in test/resources(3, 6, 8) - List expectedTrlValues = List.of(3, 6, 8); + Collection expectedTrlValues = List.of(3, 6, 8); final ResponseEntity actResponse = executor.executePost(buildPayload(buildMultipleKeys(config))); - List diagnosisKeyList = diagnosisKeyService.getDiagnosisKeys(); + final Collection diagnosisKeyList = diagnosisKeyService.getDiagnosisKeys(); diagnosisKeyList.forEach(diagnosisKey -> { assertThat(expectedTrlValues.contains(diagnosisKey.getTransmissionRiskLevel())).isTrue(); }); @@ -73,6 +74,4 @@ void testSubmissionServiceConfigValuesAreSet() { assertThat(config.getTrlDerivations()).isNotNull(); assertThat(config.getTrlDerivations().getTrlMapping()).hasSize(4); } - - } From d6ba19e04d5a71cf874dfb611fe81a158716835a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 12:12:14 +0100 Subject: [PATCH 31/83] ensure SRS is not uploaded to EFGS/CHGS --- .../EnsureSrsIsNotSendToEfgsTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/EnsureSrsIsNotSendToEfgsTest.java diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/EnsureSrsIsNotSendToEfgsTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/EnsureSrsIsNotSendToEfgsTest.java new file mode 100644 index 0000000000..52cee63bf3 --- /dev/null +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/EnsureSrsIsNotSendToEfgsTest.java @@ -0,0 +1,63 @@ +package app.coronawarn.server.common.persistence.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import app.coronawarn.server.common.persistence.domain.DiagnosisKey; +import app.coronawarn.server.common.persistence.domain.FederationUploadKey; +import app.coronawarn.server.common.protocols.external.exposurenotification.ReportType; +import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; +import java.util.Random; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; + +@DataJdbcTest +class EnsureSrsIsNotSendToEfgsTest { + + @Autowired + private DiagnosisKeyRepository repository; + + @Autowired + private FederationUploadKeyRepository efgs; + + @BeforeEach + void beforeEach() { + repository.deleteAll(); + efgs.deleteAll(); + } + + @ParameterizedTest + @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) + void checkSrsKeysWontBeUploaded(final SubmissionType type) { + final byte[] id = new byte[16]; + new Random().nextBytes(id); + + assertFalse(repository.exists(id, type.name())); + + final DiagnosisKey key = DiagnosisKey.builder() + .withKeyDataAndSubmissionType(id, type) + .withRollingStartIntervalNumber(600) + .withTransmissionRiskLevel(5) + .withRollingPeriod(1) + .withCountryCode("DE") + .withVisitedCountries(Set.of("DE")) + .withSubmissionTimestamp(0L) + .withReportType(ReportType.CONFIRMED_TEST) + .build(); + repository.saveDoNothingOnConflict( + key.getKeyData(), key.getRollingStartIntervalNumber(), key.getRollingPeriod(), + key.getSubmissionTimestamp(), key.getTransmissionRiskLevel(), + key.getOriginCountry(), key.getVisitedCountries().toArray(new String[0]), + key.getReportType().name(), key.getDaysSinceOnsetOfSymptoms(), + key.isConsentToFederation(), key.getSubmissionType().name()); + + assertTrue(repository.exists(id, type.name())); + assertThat(efgs.findAll()).doesNotContain(FederationUploadKey.from(key)); + } +} From bec78c2bcca8e5880c52f95cbc23fc89c9b0ceb5 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 14:27:01 +0100 Subject: [PATCH 32/83] filter by 'srs-days: ${MAX_NUMBER_OF_DAYS_FOR_SRS:14}' --- .../config/SubmissionServiceConfig.java | 11 ++++ .../controller/SubmissionController.java | 55 +++++++++++-------- .../src/main/resources/application.yaml | 2 + .../controller/SubmissionControllerTest.java | 3 +- .../controller/SubmissionPayloadMockData.java | 6 ++ .../src/test/resources/application.yaml | 1 + 6 files changed, 54 insertions(+), 24 deletions(-) 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 de49b7dec8..8fb9df08cc 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 @@ -35,6 +35,9 @@ public class SubmissionServiceConfig { @Min(7) @Max(28) private Integer retentionDays; + @Min(0) + @Max(28) + private int srsDays; @Min(1) @Max(25) private Integer randomKeyPaddingMultiplier; @@ -104,6 +107,14 @@ public void setRetentionDays(Integer retentionDays) { this.retentionDays = retentionDays; } + public int getSrsDays() { + return Math.min(srsDays, retentionDays); + } + + public void setSrsDays(int srsDays) { + this.srsDays = srsDays; + } + public Integer getRandomKeyPaddingMultiplier() { return randomKeyPaddingMultiplier; } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index af1972c2b9..993d4b4965 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -30,8 +30,7 @@ import feign.RetryableException; import io.micrometer.core.annotation.Timed; import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Collection; import java.util.stream.IntStream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -67,6 +66,7 @@ public class SubmissionController { private final EventTanVerifier eventTanVerifier; private final SrsOtpVerifier srsOtpVerifier; private final Integer retentionDays; + private final int srsDays; private final Integer randomKeyPaddingMultiplier; private final FakeDelayManager fakeDelayManager; private final SubmissionServiceConfig submissionServiceConfig; @@ -86,6 +86,7 @@ public class SubmissionController { this.fakeDelayManager = fakeDelayManager; this.submissionServiceConfig = submissionServiceConfig; this.retentionDays = submissionServiceConfig.getRetentionDays(); + this.srsDays = submissionServiceConfig.getSrsDays(); this.randomKeyPaddingMultiplier = submissionServiceConfig.getRandomKeyPaddingMultiplier(); this.eventCheckinFacade = eventCheckinFacade; this.trlDerivations = submissionServiceConfig.getTrlDerivations(); @@ -116,18 +117,18 @@ public DeferredResult> submitDiagnosisKey( } if (!isEmpty(otp)) { - if (!validSrsType(exposureKeys.getSubmissionType().getNumber())) { + if (!validSrsType(exposureKeys)) { return badRequest(); } if (diagnosisKeyService.countTodaysSrs() >= submissionServiceConfig.getMaxSrsPerDay()) { logger.warn("We reached the maximum number ({}) of allowed Self-Report-Submissions for today ({})!", submissionServiceConfig.getMaxSrsPerDay(), now(UTC)); - return forbidden(); + return tooManyRequests(); } return buildRealDeferredResult(exposureKeys, otp, srsOtpVerifier); } - if (!validSubmissionType(exposureKeys.getSubmissionType().getNumber())) { + if (!validSubmissionType(exposureKeys)) { return badRequest(); } @@ -140,27 +141,28 @@ public DeferredResult> submitDiagnosisKey( * Valid regular submission types: SUBMISSION_TYPE_PCR_TEST_VALUE, SUBMISSION_TYPE_RAPID_TEST_VALUE, * SUBMISSION_TYPE_HOST_WARNING_VALUE. * - * @param submissionTypeValue int value of the enum + * @param payload uses {@link SubmissionPayload#getSubmissionType()} int value of the enum * @return true if it's ok. * * @see SubmissionType */ - private boolean validSubmissionType(int submissionTypeValue) { - return 0 <= submissionTypeValue && submissionTypeValue <= SUBMISSION_TYPE_HOST_WARNING_VALUE; + private boolean validSubmissionType(final SubmissionPayload payload) { + return 0 <= payload.getSubmissionType().getNumber() + && payload.getSubmissionType().getNumber() <= SUBMISSION_TYPE_HOST_WARNING_VALUE; } /** * Valid SRS types: SUBMISSION_TYPE_SRS_SELF_TEST, SUBMISSION_TYPE_SRS_RAT, SUBMISSION_TYPE_SRS_REGISTERED_PCR, * SUBMISSION_TYPE_SRS_UNREGISTERED_PCR, SUBMISSION_TYPE_SRS_RAPID_PCR, SUBMISSION_TYPE_SRS_OTHER. * - * @param submissionTypeValue int value of the enum + * @param payload uses {@link SubmissionPayload#getSubmissionType()} int value of the enum * @return true if it's ok. * * @see SubmissionType */ - private boolean validSrsType(int submissionTypeValue) { - return SUBMISSION_TYPE_SRS_SELF_TEST_VALUE <= submissionTypeValue - && submissionTypeValue <= SUBMISSION_TYPE_SRS_OTHER_VALUE; + private boolean validSrsType(final SubmissionPayload payload) { + return SUBMISSION_TYPE_SRS_SELF_TEST_VALUE <= payload.getSubmissionType().getNumber() + && payload.getSubmissionType().getNumber() <= SUBMISSION_TYPE_SRS_OTHER_VALUE; } /** @@ -172,8 +174,8 @@ private DeferredResult> badRequest() { return new DeferredResult<>(null, () -> ResponseEntity.badRequest().build()); } - private DeferredResult> forbidden() { - return new DeferredResult<>(null, () -> ResponseEntity.status(HttpStatus.FORBIDDEN).build()); + private DeferredResult> tooManyRequests() { + return new DeferredResult<>(null, () -> ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build()); } /** @@ -217,7 +219,7 @@ private DeferredResult> buildRealDeferredResult(SubmissionP extractAndStoreDiagnosisKeys(submissionPayload); CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(submissionPayload); - if (validSrsType(submissionPayload.getSubmissionType().getNumber())) { + if (validSrsType(submissionPayload)) { diagnosisKeyService.recordSrs(submissionPayload.getSubmissionType()); } @@ -242,9 +244,9 @@ private DeferredResult> buildRealDeferredResult(SubmissionP } private void extractAndStoreDiagnosisKeys(SubmissionPayload submissionPayload) { - List diagnosisKeys = extractValidDiagnosisKeysFromPayload( + final Collection diagnosisKeys = extractValidDiagnosisKeysFromPayload( enhanceWithDefaultValuesIfMissing(submissionPayload)); - for (DiagnosisKey diagnosisKey : diagnosisKeys) { + for (final DiagnosisKey diagnosisKey : diagnosisKeys) { mapTrasmissionRiskValue(diagnosisKey); } diagnosisKeyService.saveDiagnosisKeys(padDiagnosisKeys(diagnosisKeys)); @@ -255,10 +257,11 @@ private void mapTrasmissionRiskValue(DiagnosisKey diagnosisKey) { trlDerivations.mapFromTrlSubmittedToTrlToStore(diagnosisKey.getTransmissionRiskLevel())); } - private List extractValidDiagnosisKeysFromPayload(SubmissionPayload submissionPayload) { - List protoBufferKeys = submissionPayload.getKeysList(); + private Collection extractValidDiagnosisKeysFromPayload(SubmissionPayload submissionPayload) { + final Collection protoBufferKeys = submissionPayload.getKeysList(); - List diagnosisKeys = protoBufferKeys.stream() + final boolean isOfficialSubmission = validSubmissionType(submissionPayload); + final Collection diagnosisKeys = protoBufferKeys.stream() .filter(protoBufferKey -> rollingStartIntervalNumberValidator .isValid(protoBufferKey.getRollingStartIntervalNumber(), null)) .map(protoBufferKey -> DiagnosisKey.builder() @@ -272,7 +275,8 @@ private List extractValidDiagnosisKeysFromPayload(SubmissionPayloa .build() ) .filter(diagnosisKey -> diagnosisKey.isYoungerThanRetentionThreshold(retentionDays)) - .collect(Collectors.toList()); + .filter(diagnosisKey -> isOfficialSubmission || diagnosisKey.isYoungerThanRetentionThreshold(srsDays)) + .toList(); if (protoBufferKeys.size() > diagnosisKeys.size()) { logger.warn("Not persisting {} one or more diagnosis key(s), as it is outdated beyond retention threshold or the " @@ -300,8 +304,13 @@ private String defaultIfEmptyOriginCountry(String originCountry) { return StringUtils.defaultIfBlank(originCountry, submissionServiceConfig.getDefaultOriginCountry()); } - private List padDiagnosisKeys(List diagnosisKeys) { - List paddedDiagnosisKeys = new ArrayList<>(); + private Collection padDiagnosisKeys(final Collection diagnosisKeys) { + if (randomKeyPaddingMultiplier <= 1) { + // no padding required + return diagnosisKeys; + } + final Collection paddedDiagnosisKeys = new ArrayList<>( + diagnosisKeys.size() * randomKeyPaddingMultiplier); diagnosisKeys.forEach(diagnosisKey -> { paddedDiagnosisKeys.add(diagnosisKey); IntStream.range(1, randomKeyPaddingMultiplier) diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 8f945204dd..5a176b1428 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -20,6 +20,8 @@ services: fake-delay-moving-average-samples: 10 # The retention threshold for acceptable diagnosis keys during submission. retention-days: 14 + # How many days in the past are keys of a self reported submission allowed. + srs-days: ${MAX_NUMBER_OF_DAYS_FOR_SRS:14} # The number of keys to save to the DB for every real submitted key. # Example: If the 'random-key-padding-multiplier' is set to 10, and 5 keys are being submitted, # then the 5 real submitted keys will be saved to the DB, plus an additional 45 keys with 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 0f1f9a4c51..fe15c653d6 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 @@ -36,6 +36,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 org.springframework.http.HttpStatus.TOO_MANY_REQUESTS; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.persistence.domain.TraceTimeIntervalWarning; @@ -645,6 +646,6 @@ void testForbidTooManySrsPerDay() { when(diagnosisKeyService.countTodaysSrs()).thenReturn(config.getMaxSrsPerDay()); response = executor .executeSrsPost(buildSrsPayload(config, SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST)); - assertThat(response.getStatusCode()).isEqualTo(FORBIDDEN); + assertThat(response.getStatusCode()).isEqualTo(TOO_MANY_REQUESTS); } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java index 085557e7a2..aab636102a 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/SubmissionPayloadMockData.java @@ -171,6 +171,12 @@ public static SubmissionPayload buildPayloadWithVisitedCountries(List vi public static SubmissionPayload buildSrsPayload(final SubmissionServiceConfig config, final SubmissionType type) { return SubmissionPayload.newBuilder() .addAllKeys(buildMultipleKeys(config)) + .addAllKeys(buildMultipleKeys(new SubmissionServiceConfig() { + @Override + public Integer getRetentionDays() { + return config.getSrsDays(); + } + })) .addAllVisitedCountries(List.of("DE")) .setOrigin("DE") .setRequestPadding(ByteString.copyFrom("PaddingString".getBytes())) diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 5c571d70f5..da008d8349 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -28,6 +28,7 @@ services: initial-fake-delay-milliseconds: 1 fake-delay-moving-average-samples: 1 retention-days: 14 + srs-days: 7 random-key-padding-multiplier: 10 random-checkins-padding-multiplier: 1 random-checkins-padding-pepper: 0efbb3d683b713857750eec4b042ca1a7c50b5e4 From 7a135741f7096c748ff721dbfbe5aa877c659b10 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 15:33:37 +0100 Subject: [PATCH 33/83] set 'cwa-keys-truncated' header in case keys are older than 'srsDays' --- .../controller/SubmissionController.java | 40 +++++++++++++------ .../controller/SubmissionControllerTest.java | 23 +++++++++-- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 993d4b4965..249e0e2197 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -3,6 +3,7 @@ import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING_VALUE; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_OTHER_VALUE; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST_VALUE; +import static java.lang.String.valueOf; import static java.time.LocalDate.now; import static java.time.ZoneOffset.UTC; import static org.springframework.util.ObjectUtils.isEmpty; @@ -37,6 +38,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.ResponseEntity.BodyBuilder; import org.springframework.util.StopWatch; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; @@ -59,6 +61,7 @@ public class SubmissionController { private static final Logger logger = LoggerFactory.getLogger(SubmissionController.class); public static final String CWA_FILTERED_CHECKINS_HEADER = "cwa-filtered-checkins"; public static final String CWA_SAVED_CHECKINS_HEADER = "cwa-saved-checkins"; + public static final String CWA_KEYS_TRUNCATED_HEADER = "cwa-keys-truncated"; private final SubmissionMonitor submissionMonitor; private final DiagnosisKeyService diagnosisKeyService; @@ -216,17 +219,18 @@ private DeferredResult> buildRealDeferredResult(SubmissionP submissionMonitor.incrementInvalidTanRequestCounter(); deferredResult.setResult(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); } else { - extractAndStoreDiagnosisKeys(submissionPayload); + final BodyBuilder response = ResponseEntity.ok(); + extractAndStoreDiagnosisKeys(submissionPayload, response); + CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(submissionPayload); if (validSrsType(submissionPayload)) { diagnosisKeyService.recordSrs(submissionPayload.getSubmissionType()); } - deferredResult.setResult(ResponseEntity.ok() - .header(CWA_FILTERED_CHECKINS_HEADER, String.valueOf(checkinsStorageResult.getNumberOfFilteredCheckins())) - .header(CWA_SAVED_CHECKINS_HEADER, String.valueOf(checkinsStorageResult.getNumberOfSavedCheckins())) - .build()); + response.header(CWA_FILTERED_CHECKINS_HEADER, valueOf(checkinsStorageResult.getNumberOfFilteredCheckins())) + .header(CWA_SAVED_CHECKINS_HEADER, valueOf(checkinsStorageResult.getNumberOfSavedCheckins())); + deferredResult.setResult(response.build()); } } catch (RetryableException e) { logger.error("Verification Service could not be reached after retry mechanism.", e); @@ -243,9 +247,9 @@ private DeferredResult> buildRealDeferredResult(SubmissionP return deferredResult; } - private void extractAndStoreDiagnosisKeys(SubmissionPayload submissionPayload) { + private void extractAndStoreDiagnosisKeys(final SubmissionPayload submissionPayload, final BodyBuilder response) { final Collection diagnosisKeys = extractValidDiagnosisKeysFromPayload( - enhanceWithDefaultValuesIfMissing(submissionPayload)); + enhanceWithDefaultValuesIfMissing(submissionPayload), response); for (final DiagnosisKey diagnosisKey : diagnosisKeys) { mapTrasmissionRiskValue(diagnosisKey); } @@ -257,11 +261,11 @@ private void mapTrasmissionRiskValue(DiagnosisKey diagnosisKey) { trlDerivations.mapFromTrlSubmittedToTrlToStore(diagnosisKey.getTransmissionRiskLevel())); } - private Collection extractValidDiagnosisKeysFromPayload(SubmissionPayload submissionPayload) { + private Collection extractValidDiagnosisKeysFromPayload(final SubmissionPayload submissionPayload, + final BodyBuilder response) { final Collection protoBufferKeys = submissionPayload.getKeysList(); - final boolean isOfficialSubmission = validSubmissionType(submissionPayload); - final Collection diagnosisKeys = protoBufferKeys.stream() + Collection diagnosisKeys = protoBufferKeys.stream() .filter(protoBufferKey -> rollingStartIntervalNumberValidator .isValid(protoBufferKey.getRollingStartIntervalNumber(), null)) .map(protoBufferKey -> DiagnosisKey.builder() @@ -272,12 +276,22 @@ private Collection extractValidDiagnosisKeysFromPayload(Submission submissionPayload.getOrigin(), submissionPayload.getConsentToFederation()) .withFieldNormalization(new SubmissionKeyNormalizer(submissionServiceConfig)) - .build() - ) + .build()) .filter(diagnosisKey -> diagnosisKey.isYoungerThanRetentionThreshold(retentionDays)) - .filter(diagnosisKey -> isOfficialSubmission || diagnosisKey.isYoungerThanRetentionThreshold(srsDays)) .toList(); + if (validSrsType(submissionPayload)) { + final Collection keys = diagnosisKeys.stream() + .filter(diagnosisKey -> diagnosisKey.isYoungerThanRetentionThreshold(srsDays)).toList(); + if (keys.size() < diagnosisKeys.size()) { + response.header(CWA_KEYS_TRUNCATED_HEADER, valueOf(srsDays)); + logger.warn( + "Not persisting '{}' self reported key(s), because they are older than '{}' days.", + diagnosisKeys.size() - keys.size(), srsDays); + diagnosisKeys = keys; + } + } + if (protoBufferKeys.size() > diagnosisKeys.size()) { logger.warn("Not persisting {} one or more diagnosis key(s), as it is outdated beyond retention threshold or the " + "RollingStartIntervalNumber is in the future. Payload: {}", 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 fe15c653d6..8a34f499ed 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 @@ -1,6 +1,8 @@ package app.coronawarn.server.services.submission.controller; import static app.coronawarn.server.common.persistence.service.utils.checkins.CheckinsDateSpecification.TEN_MINUTE_INTERVAL_DERIVATION; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST; +import static app.coronawarn.server.services.submission.controller.SubmissionController.CWA_KEYS_TRUNCATED_HEADER; 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.buildKeyWithFutureInterval; @@ -81,6 +83,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.ResponseEntity.BodyBuilder; import org.springframework.test.annotation.DirtiesContext; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @@ -639,13 +642,27 @@ private static Stream validVisitedCountries() { * @see SubmissionController#submitDiagnosisKey(SubmissionPayload, String, String) */ @Test - void testForbidTooManySrsPerDay() { + void testTooManySrsPerDay() { ResponseEntity response = executor - .executeSrsPost(buildSrsPayload(config, SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST)); + .executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); assertThat(response.getStatusCode()).isEqualTo(OK); when(diagnosisKeyService.countTodaysSrs()).thenReturn(config.getMaxSrsPerDay()); response = executor - .executeSrsPost(buildSrsPayload(config, SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST)); + .executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); assertThat(response.getStatusCode()).isEqualTo(TOO_MANY_REQUESTS); } + + /** + * @see SubmissionController#extractValidDiagnosisKeysFromPayload(SubmissionPayload, BodyBuilder) + */ + @Test + void testSrsKeysTruncated() { + final ResponseEntity response = executor.executeSrsPost( + // creates 3 keys around 14 days, which will be filtered and 3 more around the 'srsDays' + buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(OK); + assertThat(response.getHeaders().keySet()).contains(CWA_KEYS_TRUNCATED_HEADER); + assertThat(response.getHeaders().get(CWA_KEYS_TRUNCATED_HEADER)) + .isEqualTo(List.of(String.valueOf(config.getSrsDays()))); + } } From 1b1ce313f4ef87934067c23fc756195223a88e11 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 15:43:41 +0100 Subject: [PATCH 34/83] retention policy for 'self_report_submissions' table srs-type-statistics-days: ${SRS_TYPE_STATISTICS_DAYS:400} --- .../config/DistributionServiceConfig.java | 11 +++++++++++ .../services/distribution/runner/RetentionPolicy.java | 5 ++++- .../distribution/src/main/resources/application.yaml | 2 ++ .../runner/RetentionPolicyRunnerTest.java | 2 ++ .../distribution/src/test/resources/application.yaml | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) 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 8c6de4868e..73633b90d4 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 @@ -45,6 +45,9 @@ public class DistributionServiceConfig { @Min(0) @Max(28) private Integer retentionDays; + @Min(0) + @Max(4000) + private int srsTypeStatisticsDays; @Min(120) @Max(720) private Integer expiryPolicyMinutes; @@ -110,6 +113,14 @@ public void setRetentionDays(Integer retentionDays) { this.retentionDays = retentionDays; } + public int getSrsTypeStatisticsDays() { + return srsTypeStatisticsDays; + } + + public void setSrsTypeStatisticsDays(final int srsTypeStatisticsDays) { + this.srsTypeStatisticsDays = srsTypeStatisticsDays; + } + public Integer getDaysToPublish() { return daysToPublish == null ? retentionDays : daysToPublish; } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java index d2085cfd65..91684fc6d7 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/RetentionPolicy.java @@ -31,6 +31,8 @@ public class RetentionPolicy implements ApplicationRunner { private final Integer retentionDays; + private final int srsTypeStatisticsDays; + private final S3RetentionPolicy s3RetentionPolicy; private final Integer hourFileRetentionDays; @@ -57,6 +59,7 @@ public RetentionPolicy( this.traceTimeIntervalWarningService = traceTimeIntervalWarningService; this.applicationContext = applicationContext; this.retentionDays = distributionServiceConfig.getRetentionDays(); + this.srsTypeStatisticsDays = distributionServiceConfig.getSrsTypeStatisticsDays(); this.hourFileRetentionDays = distributionServiceConfig.getObjectStore().getHourFileRetentionDays(); this.s3RetentionPolicy = s3RetentionPolicy; this.statisticsDownloadService = statisticsDownloadService; @@ -66,7 +69,7 @@ public RetentionPolicy( public void run(ApplicationArguments args) { try { diagnosisKeyService.applyRetentionPolicy(retentionDays); - diagnosisKeyService.applySrsRetentionPolicy(retentionDays /* FIXME - same amount? */); + diagnosisKeyService.applySrsRetentionPolicy(srsTypeStatisticsDays); traceTimeIntervalWarningService.applyRetentionPolicy(retentionDays); s3RetentionPolicy.applyDiagnosisKeyDayRetentionPolicy(retentionDays); s3RetentionPolicy.applyDiagnosisKeyHourRetentionPolicy(hourFileRetentionDays); diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index 5ab3bef580..9031bb7037 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -22,6 +22,8 @@ services: output-file-name-v2: index-v2 # The number of days to retain diagnosis keys and trace time warnings for both database persistency layer and files stored on the object store. retention-days: 14 + # The number of days to keep 'submisson_type' records in 'self_report_submissions' table. + srs-type-statistics-days: ${SRS_TYPE_STATISTICS_DAYS:400} # The number of days for diagnosis keys (based upon submission timestamp) to publish on CDN. days-to-publish: ${DAYS_TO_PUBLISH:10} # The number of minutes that diagnosis keys must have been expired for (since the end of the rolling interval window) before they can be distributed. diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunnerTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunnerTest.java index f043061f60..5090c83a4e 100644 --- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunnerTest.java +++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/runner/RetentionPolicyRunnerTest.java @@ -56,6 +56,8 @@ void shouldCallDatabaseAndS3RetentionRunner() { .applyRetentionPolicy(distributionServiceConfig.getRetentionDays()); verify(diagnosisKeyService, times(1)) .applyRetentionPolicy(distributionServiceConfig.getRetentionDays()); + verify(diagnosisKeyService, times(1)) + .applySrsRetentionPolicy(distributionServiceConfig.getSrsTypeStatisticsDays()); verify(traceTimeIntervalWarningService, times(1)) .applyRetentionPolicy(distributionServiceConfig.getRetentionDays()); verify(s3RetentionPolicy, times(1)) diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 772759e0ac..8c16c20520 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -11,6 +11,7 @@ services: output-file-name: index output-file-name-v2: index-v2 retention-days: 14 + srs-type-statistics-days: 400 expiry-policy-minutes: 120 shifting-policy-threshold: 5 maximum-number-of-keys-per-bundle: 600000 From cb6815a072bb5a5566ed85f9c24b5ea5ff492cca Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 17:20:14 +0100 Subject: [PATCH 35/83] better test readability --- .../controller/SubmissionControllerTest.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) 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 8a34f499ed..452f8decc0 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 @@ -116,7 +116,6 @@ class SubmissionControllerTest { @Autowired private TraceTimeIntervalWarningRepository traceTimeIntervalWarningRepository; - @BeforeEach public void setUpMocks() { traceTimeIntervalWarningRepository.deleteAll(); @@ -125,7 +124,6 @@ public void setUpMocks() { when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); } - private void assertDSOSCorrectlyComputedFromTRL(final SubmissionServiceConfig config, final Collection submittedTEKs, final Collection diagnosisKeys) { submittedTEKs.stream().map(tek -> Pair.of(tek, findDiagnosisKeyMatch(tek, diagnosisKeys))).forEach(pair -> { @@ -206,7 +204,6 @@ private DiagnosisKey findDiagnosisKeyMatch(final TemporaryExposureKey temporaryE .findFirst().orElseThrow(); } - @ParameterizedTest @MethodSource("createIncompleteHeaders") void badRequestIfCwaHeadersMissing(final HttpHeaders headers) { @@ -429,7 +426,7 @@ void submmissionPayloadWithInvalidIntervalItemsIsFilteredCorrectly() { verify(diagnosisKeyService, atLeastOnce()).saveDiagnosisKeys(argument.capture()); Collection savedDiagnosisKeys = argument.getValue(); - int expectedNumberofSavedKeys = (submittedKeys.size()-1) * config.getRandomKeyPaddingMultiplier(); + int expectedNumberofSavedKeys = (submittedKeys.size() - 1) * config.getRandomKeyPaddingMultiplier(); assertThat(savedDiagnosisKeys).hasSize(expectedNumberofSavedKeys); assertThat(savedDiagnosisKeys.stream().anyMatch(diagnosisKey -> diagnosisKey.getRollingStartIntervalNumber() == futureKey.getRollingStartIntervalNumber()) == false); } @@ -484,8 +481,8 @@ void testCheckinDataHeadersAreCorrectlyFilled() { final List checkins = List .of(CheckIn.newBuilder().setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInThePast)) - .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast)) - .setTransmissionRiskLevel(1).setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build(), + .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast)) + .setTransmissionRiskLevel(1).setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build(), CheckIn.newBuilder().setTransmissionRiskLevel(3) .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInAllowedPeriod)) .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInAllowedPeriod) + 10) @@ -609,7 +606,6 @@ void testValidVisitedCountriesSubmissionPayload(final List visitedCountr assertThat(actResponse.getStatusCode()).isEqualTo(OK); } - public static SubmissionPayload buildPayloadWithInvalidKey() { final TemporaryExposureKey invalidKey = buildTemporaryExposureKey(VALID_KEY_DATA_1, createRollingStartIntervalNumber(2), 999, ReportType.CONFIRMED_TEST, 1); @@ -661,8 +657,8 @@ void testSrsKeysTruncated() { // creates 3 keys around 14 days, which will be filtered and 3 more around the 'srsDays' buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); assertThat(response.getStatusCode()).isEqualTo(OK); - assertThat(response.getHeaders().keySet()).contains(CWA_KEYS_TRUNCATED_HEADER); - assertThat(response.getHeaders().get(CWA_KEYS_TRUNCATED_HEADER)) - .isEqualTo(List.of(String.valueOf(config.getSrsDays()))); + assertThat(response.getHeaders()).containsKey(CWA_KEYS_TRUNCATED_HEADER); + assertThat(response.getHeaders()).containsEntry(CWA_KEYS_TRUNCATED_HEADER, + List.of(String.valueOf(config.getSrsDays()))); } } From 084ccd761aee9b7adc8f23091b0314313363e7d1 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 2 Dec 2022 17:21:12 +0100 Subject: [PATCH 36/83] lauch configs for eclipse --- .../Eclipse/spring-boot-callback.launch | 8 +-- ...-dcc_revocation-fake-dcc-revocation.launch | 49 +++++++++++++++++++ .../Eclipse/spring-boot-submission.launch | 13 +++-- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 runconfigs/Eclipse/spring-boot-dcc_revocation-fake-dcc-revocation.launch diff --git a/runconfigs/Eclipse/spring-boot-callback.launch b/runconfigs/Eclipse/spring-boot-callback.launch index 4c1aea34eb..974b9622fe 100644 --- a/runconfigs/Eclipse/spring-boot-callback.launch +++ b/runconfigs/Eclipse/spring-boot-callback.launch @@ -1,5 +1,6 @@ + @@ -13,6 +14,7 @@ + @@ -27,13 +29,13 @@ - - + + - + diff --git a/runconfigs/Eclipse/spring-boot-dcc_revocation-fake-dcc-revocation.launch b/runconfigs/Eclipse/spring-boot-dcc_revocation-fake-dcc-revocation.launch new file mode 100644 index 0000000000..2d450aefb1 --- /dev/null +++ b/runconfigs/Eclipse/spring-boot-dcc_revocation-fake-dcc-revocation.launch @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/runconfigs/Eclipse/spring-boot-submission.launch b/runconfigs/Eclipse/spring-boot-submission.launch index 80ed697f56..134c1b2fb7 100644 --- a/runconfigs/Eclipse/spring-boot-submission.launch +++ b/runconfigs/Eclipse/spring-boot-submission.launch @@ -1,5 +1,6 @@ + @@ -13,6 +14,7 @@ + @@ -27,14 +29,15 @@ - - - + + + + + - - + From abe5c108bc4748dcc4823c2b0ad662848e936b43 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 6 Dec 2022 14:39:16 +0100 Subject: [PATCH 37/83] switch to com.github.erosb:everit-json-schema:1.14.1 --- common/shared/pom.xml | 4 ++-- pom.xml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/shared/pom.xml b/common/shared/pom.xml index f47c4567cb..b4e5c2687e 100644 --- a/common/shared/pom.xml +++ b/common/shared/pom.xml @@ -21,8 +21,8 @@ jackson-dataformat-cbor - org.everit.json - org.everit.json.schema + com.github.erosb + everit-json-schema org.bouncycastle diff --git a/pom.xml b/pom.xml index ff77dfe349..072299da5a 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 5.7.5 3.21.9 1.1.1 - 1.5.1 + 1.14.1 1.9.4 2.11.0 2.19.0 @@ -262,9 +262,9 @@ ${spring-retry.version} - org.everit.json - org.everit.json.schema - ${everit-json.version} + com.github.erosb + everit-json-schema + ${everit-json-schema.version} org.json From a7ecdef27543f7a4f20541cb442535e38a2c3ffa Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 6 Dec 2022 14:40:24 +0100 Subject: [PATCH 38/83] switch to org.bouncycastle:bcpkix-jdk18on:1.72 --- common/shared/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/shared/pom.xml b/common/shared/pom.xml index b4e5c2687e..371f185e8c 100644 --- a/common/shared/pom.xml +++ b/common/shared/pom.xml @@ -26,7 +26,7 @@ org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on org.slf4j diff --git a/pom.xml b/pom.xml index 072299da5a..7ab1bef0e5 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 1.7.36 12.1 3.6.1 - 1.70 + 1.72 2.18.6 4.1.85.Final 31.1-jre @@ -243,7 +243,7 @@ org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on ${bouncycastle.version} From 4072ae348dab22842e9e5b7bd65ab58bac555de5 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 6 Dec 2022 14:41:16 +0100 Subject: [PATCH 39/83] switch to org.junit.jupiter:junit-jupiter:5.9.1 --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 7ab1bef0e5..0e7137f40e 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ 2.35.0 4.5.14 4.13.2 + 5.9.1 9.0.69 0.2.6 10.5.0 @@ -345,6 +346,11 @@ ${junit.version} test + + org.junit.jupiter + junit-jupiter + ${jupiter.version} + org.apache.tomcat.embed tomcat-embed-core From 6df8c77e13c51bf3081749afc29c9ea5a5b92206 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 6 Dec 2022 14:42:37 +0100 Subject: [PATCH 40/83] monitoring: add selfReports counter --- .../submission/controller/SubmissionController.java | 3 +++ .../services/submission/monitoring/SubmissionMonitor.java | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 249e0e2197..2806fe7d70 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -128,6 +128,9 @@ public DeferredResult> submitDiagnosisKey( submissionServiceConfig.getMaxSrsPerDay(), now(UTC)); return tooManyRequests(); } + submissionMonitor.incrementRequestCounter(); + submissionMonitor.incrementRealRequestCounter(); + submissionMonitor.incrementSelfReportSubmissions(); return buildRealDeferredResult(exposureKeys, otp, srsOtpVerifier); } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SubmissionMonitor.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SubmissionMonitor.java index 6026923c0c..b0b16c4eee 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SubmissionMonitor.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SubmissionMonitor.java @@ -1,5 +1,3 @@ - - package app.coronawarn.server.services.submission.monitoring; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; @@ -25,6 +23,7 @@ public class SubmissionMonitor { private BatchCounter fakeRequests; private BatchCounter invalidTanRequests; private BatchCounter submissionOnBehalfRequests; + private BatchCounter srsRequests; /** * Constructor for {@link SubmissionMonitor}. Initializes all counters to 0 upon being called. @@ -57,6 +56,7 @@ private void initializeCounters() { fakeRequests = new BatchCounter(meterRegistry, batchSize, "fake"); invalidTanRequests = new BatchCounter(meterRegistry, batchSize, "invalidTan"); submissionOnBehalfRequests = new BatchCounter(meterRegistry, batchSize, "submissionOnBehalf"); + srsRequests = new BatchCounter(meterRegistry, batchSize, "selfReports"); } /** @@ -91,4 +91,8 @@ public void incrementInvalidTanRequestCounter() { public void incrementSubmissionOnBehalfCounter() { submissionOnBehalfRequests.increment(); } + + public void incrementSelfReportSubmissions() { + srsRequests.increment(); + } } From 1b31568ae88f42d8a5f105559d2b8bcd530adac2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 6 Dec 2022 15:07:24 +0100 Subject: [PATCH 41/83] org.everit.json.schema.SchemaException: #: could not determine version https://stackoverflow.com/questions/69666910/org-everit-json-schema-schemaexception-could-not-determine-version --- common/shared/src/test/resources/dgc/ccl-configuration.json | 2 +- common/shared/src/test/resources/validation_schema.json | 2 +- .../distribution/src/main/resources/dgc/ccl-configuration.json | 2 +- .../src/main/resources/dgc/dcc-validation-rule.json | 2 +- .../src/main/resources/dgc/jfn-function-descriptor.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/shared/src/test/resources/dgc/ccl-configuration.json b/common/shared/src/test/resources/dgc/ccl-configuration.json index 9aa2cf72a4..a833d86257 100644 --- a/common/shared/src/test/resources/dgc/ccl-configuration.json +++ b/common/shared/src/test/resources/dgc/ccl-configuration.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "https://ccl.coronawarn.app/ccl-configuration.json", "title": "CWA CCL Configuration", "type": "object", diff --git a/common/shared/src/test/resources/validation_schema.json b/common/shared/src/test/resources/validation_schema.json index 5088a9f4aa..c9cf89860d 100644 --- a/common/shared/src/test/resources/validation_schema.json +++ b/common/shared/src/test/resources/validation_schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "serialization_validation_test", "title": "Validation test", "type": "object", diff --git a/services/distribution/src/main/resources/dgc/ccl-configuration.json b/services/distribution/src/main/resources/dgc/ccl-configuration.json index 2559ace8cf..1f99eb0ece 100644 --- a/services/distribution/src/main/resources/dgc/ccl-configuration.json +++ b/services/distribution/src/main/resources/dgc/ccl-configuration.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "https://ccl.coronawarn.app/ccl-configuration.json", "title": "CWA CCL Configuration", "type": "object", diff --git a/services/distribution/src/main/resources/dgc/dcc-validation-rule.json b/services/distribution/src/main/resources/dgc/dcc-validation-rule.json index d103b9dafe..5c00e47c05 100644 --- a/services/distribution/src/main/resources/dgc/dcc-validation-rule.json +++ b/services/distribution/src/main/resources/dgc/dcc-validation-rule.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "https://webgate.acceptance.ec.europa.eu/dgcg-json-api/validation-rule.schema.json", "title": "EU DCC Validation Rule", "description": "Rule to validate an issued EU Digital Covid Certificate.", diff --git a/services/distribution/src/main/resources/dgc/jfn-function-descriptor.json b/services/distribution/src/main/resources/dgc/jfn-function-descriptor.json index b81fe256fd..262fb5600b 100644 --- a/services/distribution/src/main/resources/dgc/jfn-function-descriptor.json +++ b/services/distribution/src/main/resources/dgc/jfn-function-descriptor.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "https://jfn.coronawarn.app/jfn-function-descriptor.json", "title": "JFN - Function Descriptor", "description": "JsonFunctions (JFN) - Function Descriptor", From 9218b2d007e961764ddd850d65cfe65f85c7b18f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 7 Dec 2022 08:33:43 +0100 Subject: [PATCH 42/83] postgresql:11.5 everywhere --- common/persistence/src/test/resources/application.yaml | 2 +- services/callback/src/test/resources/application.yaml | 2 +- .../src/test/resources/application-allow-list-invalid.yaml | 2 +- services/distribution/src/test/resources/application.yaml | 2 +- services/download/src/test/resources/application.yaml | 2 +- .../resources/application-disable-unencrypted-checkins.yaml | 2 +- services/submission/src/test/resources/application.yaml | 2 +- services/upload/src/test/resources/application.yaml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/persistence/src/test/resources/application.yaml b/common/persistence/src/test/resources/application.yaml index 97414e635c..65715541df 100644 --- a/common/persistence/src/test/resources/application.yaml +++ b/common/persistence/src/test/resources/application.yaml @@ -6,7 +6,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: # Use datasource as defined above. diff --git a/services/callback/src/test/resources/application.yaml b/services/callback/src/test/resources/application.yaml index 94473b77bf..b8acb601a9 100644 --- a/services/callback/src/test/resources/application.yaml +++ b/services/callback/src/test/resources/application.yaml @@ -13,7 +13,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: # Use datasource as defined above. diff --git a/services/distribution/src/test/resources/application-allow-list-invalid.yaml b/services/distribution/src/test/resources/application-allow-list-invalid.yaml index 641d1dcf23..202d4323cf 100644 --- a/services/distribution/src/test/resources/application-allow-list-invalid.yaml +++ b/services/distribution/src/test/resources/application-allow-list-invalid.yaml @@ -224,7 +224,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///cwa?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///cwa?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: # Use datasource as defined above. diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 8c16c20520..32de58b9a7 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -263,7 +263,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///cwa?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///cwa?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: # Use datasource as defined above. diff --git a/services/download/src/test/resources/application.yaml b/services/download/src/test/resources/application.yaml index 863564f95b..371e57b141 100644 --- a/services/download/src/test/resources/application.yaml +++ b/services/download/src/test/resources/application.yaml @@ -8,7 +8,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: # Use datasource as defined above. diff --git a/services/submission/src/test/resources/application-disable-unencrypted-checkins.yaml b/services/submission/src/test/resources/application-disable-unencrypted-checkins.yaml index a8e7a826da..1e3c8d20e8 100644 --- a/services/submission/src/test/resources/application-disable-unencrypted-checkins.yaml +++ b/services/submission/src/test/resources/application-disable-unencrypted-checkins.yaml @@ -19,7 +19,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: replace: none diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index da008d8349..0e85de8712 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -19,7 +19,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: replace: none diff --git a/services/upload/src/test/resources/application.yaml b/services/upload/src/test/resources/application.yaml index 9dcf905428..9a7bb157b1 100644 --- a/services/upload/src/test/resources/application.yaml +++ b/services/upload/src/test/resources/application.yaml @@ -46,7 +46,7 @@ spring: mixed: true datasource: driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:postgresql:11.8:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql + url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw?TC_INITSCRIPT=file:src/test/java/V0__init_db.sql test: database: # Use datasource as defined above. From 705480ea577c19428add575b44be351b22302b21 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 7 Dec 2022 12:07:08 +0100 Subject: [PATCH 43/83] STATISTICS_PANDEMIC_RADAR_BMG --- .../config/DistributionServiceConfig.java | 3114 +++++++++-------- .../StatisticsToProtobufMapping.java | 7 +- .../statistics/keyfigurecard/Cards.java | 82 +- .../keyfigurecard/KeyFigureCardFactory.java | 22 +- .../factory/BoosterVaccinatedCardFactory.java | 5 + .../factory/EmptyCardFactory.java | 6 + .../factory/FirstVaccinationCardFactory.java | 5 + .../factory/FullyVaccinatedCardFactory.java | 5 + .../factory/HeaderCardFactory.java | 4 + .../HospitalizationIncidenceCardFactory.java | 5 + .../factory/IncidenceCardFactory.java | 5 + .../factory/InfectionsCardFactory.java | 5 + .../factory/IntensiveCareCardFactory.java | 5 + .../factory/JoinedIncidenceCardFactory.java | 5 + .../factory/KeySubmissionCardFactory.java | 6 +- .../factory/LinkCardFactory.java | 12 +- .../ReproductionNumberCardFactory.java | 5 + .../factory/VaccinationDosesCardFactory.java | 5 + .../src/main/resources/application.yaml | 3 +- .../src/test/resources/application.yaml | 3 +- 20 files changed, 1731 insertions(+), 1578 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 73633b90d4..9c66ab2e42 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 @@ -24,1110 +24,948 @@ @Validated public class DistributionServiceConfig { - private static final String PATH_REGEX = "^[/]?[a-zA-Z0-9_]{1,1024}(/[a-zA-Z0-9_]{1,1024}){0,256}[/]?$"; - private static final String RESOURCE_REGEX = "^(classpath:|file:[/]{1,3})?([a-zA-Z0-9_/\\.]{1,1010})$"; - private static final String RESOURCE_OR_EMPTY_REGEX = "(" + RESOURCE_REGEX + ")?"; - private static final String FILE_NAME_REGEX = "^[a-zA-Z0-9_-]{1,1024}$"; - private static final String FILE_NAME_WITH_TYPE_REGEX = "^[a-zA-Z0-9_-]{1,1024}\\.[a-z]{1,64}$"; - private static final String CHAR_AND_NUMBER_REGEX = "^[a-zA-Z0-9_-]{1,1024}$"; - private static final String CHAR_NUMBER_AND_SPACE_REGEX = "^[a-zA-Z0-9_\\s]{1,32}$"; - private static final String NO_WHITESPACE_REGEX = "^[\\S]+$"; - private static final String URL_REGEX = "^http[s]?://[a-zA-Z0-9-_]{1,1024}([\\./][a-zA-Z0-9-_]{1,1024}){0,256}[/]?$"; - private static final String NUMBER_REGEX = "^[0-9]{1,256}$"; - private static final String VERSION_REGEX = "^v[0-9]{1,256}$"; - private static final String ALGORITHM_OID_REGEX = "^[0-9]{1,256}[\\.[0-9]{1,256}]{0,256}$"; - private static final String BUNDLE_REGEX = "^[a-z-]{1,256}[\\.[a-z-]{1,256}]{0,256}$"; - private static final String PRIVATE_KEY_REGEX = - "^(classpath:|file:\\.?[/]{1,8})[a-zA-Z0-9_-]{1,256}:?[/[a-zA-Z0-9_-]{1,256}]{0,256}(.pem)?$"; - - private Paths paths; - private TestData testData; - @Min(0) - @Max(28) - private Integer retentionDays; - @Min(0) - @Max(4000) - private int srsTypeStatisticsDays; - @Min(120) - @Max(720) - private Integer expiryPolicyMinutes; - @Min(0) - @Max(200) - private Integer shiftingPolicyThreshold; - @Min(600000) - @Max(750000) - private Integer maximumNumberOfKeysPerBundle; - @Pattern(regexp = FILE_NAME_REGEX) - private String outputFileName; - @Pattern(regexp = FILE_NAME_REGEX) - private String outputFileNameV2; - private Boolean includeIncompleteDays; - private Boolean includeIncompleteHours; - private String euPackageName; - private Boolean applyPoliciesForAllCountries; - private String cardIdSequence; - private TekExport tekExport; - private Signature signature; - private Api api; - private ObjectStore objectStore; - private List appFeatures; - @NotEmpty - private String[] supportedCountries; - private AppVersions appVersions; - private AppConfigParameters appConfigParameters; - private StatisticsConfig statistics; - private QrCodePosterTemplate iosQrCodePosterTemplate; - private QrCodePosterTemplate androidQrCodePosterTemplate; - private PresenceTracingParameters presenceTracingParameters; - private DigitalGreenCertificate digitalGreenCertificate; - private Integer connectionPoolSize; - private String defaultArchiveName; - private Integer minimumTrlValueAllowed; - private Integer daysToPublish; - private DccRevocation dccRevocation; - @Min(0) - @Max(100) - private int infectionThreshold; - - public Paths getPaths() { - return paths; - } - - public void setPaths(Paths paths) { - this.paths = paths; - } - - public TestData getTestData() { - return testData; - } - - public void setTestData(TestData testData) { - this.testData = testData; - } - - public Integer getRetentionDays() { - return retentionDays; - } - - public void setRetentionDays(Integer retentionDays) { - this.retentionDays = retentionDays; - } - - public int getSrsTypeStatisticsDays() { - return srsTypeStatisticsDays; - } - - public void setSrsTypeStatisticsDays(final int srsTypeStatisticsDays) { - this.srsTypeStatisticsDays = srsTypeStatisticsDays; - } - - public Integer getDaysToPublish() { - return daysToPublish == null ? retentionDays : daysToPublish; - } - - public void setDaysToPublish(Integer daysToPublish) { - this.daysToPublish = daysToPublish; - } - - public Integer getExpiryPolicyMinutes() { - return expiryPolicyMinutes; - } - - public void setExpiryPolicyMinutes(Integer expiryPolicyMinutes) { - this.expiryPolicyMinutes = expiryPolicyMinutes; - } - - public Integer getShiftingPolicyThreshold() { - return shiftingPolicyThreshold; - } - - public void setShiftingPolicyThreshold(Integer shiftingPolicyThreshold) { - this.shiftingPolicyThreshold = shiftingPolicyThreshold; - } - - public Integer getMaximumNumberOfKeysPerBundle() { - return this.maximumNumberOfKeysPerBundle; - } - - public void setMaximumNumberOfKeysPerBundle(Integer maximumNumberOfKeysPerBundle) { - this.maximumNumberOfKeysPerBundle = maximumNumberOfKeysPerBundle; - } - - public String getOutputFileName() { - return outputFileName; - } - - public void setOutputFileName(String outputFileName) { - this.outputFileName = outputFileName; - } - - public String getOutputFileNameV2() { - return outputFileNameV2; - } - - public void setOutputFileNameV2(String outputFileNameV2) { - this.outputFileNameV2 = outputFileNameV2; - } - - public Boolean getIncludeIncompleteDays() { - return includeIncompleteDays; - } - - public void setIncludeIncompleteDays(Boolean includeIncompleteDays) { - this.includeIncompleteDays = includeIncompleteDays; - } - - public Boolean getIncludeIncompleteHours() { - return includeIncompleteHours; - } - - public void setIncludeIncompleteHours(Boolean includeIncompleteHours) { - this.includeIncompleteHours = includeIncompleteHours; - } - - public String getEuPackageName() { - return euPackageName; - } - - public void setEuPackageName(String euPackageName) { - this.euPackageName = euPackageName; - } - - public Boolean getApplyPoliciesForAllCountries() { - return applyPoliciesForAllCountries; - } - - public void setApplyPoliciesForAllCountries(Boolean applyPoliciesForAllCountries) { - this.applyPoliciesForAllCountries = applyPoliciesForAllCountries; - } - - public String getCardIdSequence() { - return cardIdSequence; - } - - public void setCardIdSequence(String cardIdSequence) { - this.cardIdSequence = cardIdSequence; - } - - public TekExport getTekExport() { - return tekExport; - } - - public void setTekExport(TekExport tekExport) { - this.tekExport = tekExport; - } - - public Signature getSignature() { - return signature; - } - - public void setSignature(Signature signature) { - this.signature = signature; - } + public static class AllowList { - public Api getApi() { - return api; - } + @JsonInclude(Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class CertificateAllowList { - public void setApi(Api api) { - this.api = api; - } + private String serviceProvider; + private String hostname; + private String fingerprint256; - public ObjectStore getObjectStore() { - return objectStore; - } + @JsonProperty("serviceProviderAllowlistEndpoint") + private String serviceProviderAllowlistEndpoint; - public void setObjectStore( - ObjectStore objectStore) { - this.objectStore = objectStore; - } + public String getFingerprint256() { + return fingerprint256; + } - public QrCodePosterTemplate getIosQrCodePosterTemplate() { - return iosQrCodePosterTemplate; - } + public String getHostname() { + return hostname; + } - public void setIosQrCodePosterTemplate(QrCodePosterTemplate iosQrCodePosterTemplate) { - this.iosQrCodePosterTemplate = iosQrCodePosterTemplate; - } + public String getServiceProvider() { + return serviceProvider; + } - public QrCodePosterTemplate getAndroidQrCodePosterTemplate() { - return androidQrCodePosterTemplate; - } + public String getServiceProviderAllowlistEndpoint() { + return serviceProviderAllowlistEndpoint; + } - public void setAndroidQrCodePosterTemplate(QrCodePosterTemplate androidQrCodePosterTemplate) { - this.androidQrCodePosterTemplate = androidQrCodePosterTemplate; - } + @JsonProperty("fingerprint256") + public void setFingerprint256(final String fingerprint256) { + this.fingerprint256 = fingerprint256; + } - public List getAppFeatures() { - return appFeatures; - } + @JsonProperty("hostname") + public void setHostname(final String hostname) { + this.hostname = hostname; + } - public void setAppFeatures(List appFeatures) { - this.appFeatures = appFeatures; - } + @JsonProperty("serviceProvider") + public void setServiceProvider(final String serviceProvider) { + this.serviceProvider = serviceProvider; + } - public String[] getSupportedCountries() { - return supportedCountries; - } + public void setServiceProviderAllowlistEndpoint(final String serviceProviderAllowlistEndpoint) { + this.serviceProviderAllowlistEndpoint = serviceProviderAllowlistEndpoint; + } + } - public void setSupportedCountries(String[] supportedCountries) { - this.supportedCountries = supportedCountries; - } + @JsonInclude(Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ServiceProvider { - public AppVersions getAppVersions() { - return appVersions; - } + @JsonProperty("serviceProviderAllowlistEndpoint") + private String serviceProviderAllowlistEndpoint; + @JsonProperty("fingerprint256") + private String fingerprint256; - public void setAppVersions(AppVersions appVersions) { - this.appVersions = appVersions; - } + public String getFingerprint256() { + return fingerprint256; + } - public AppConfigParameters getAppConfigParameters() { - return appConfigParameters; - } + public String getServiceProviderAllowlistEndpoint() { + return serviceProviderAllowlistEndpoint; + } - public void setAppConfigParameters(AppConfigParameters appConfigParameters) { - this.appConfigParameters = appConfigParameters; - } + public void setFingerprint256(final String fingerprint256) { + this.fingerprint256 = fingerprint256; + } - public StatisticsConfig getStatistics() { - return statistics; - } + public void setServiceProviderAllowlistEndpoint(final String serviceProviderAllowlistEndpoint) { + this.serviceProviderAllowlistEndpoint = serviceProviderAllowlistEndpoint; + } + } - public void setStatistics(StatisticsConfig statistics) { - this.statistics = statistics; - } + private List certificates; - public Integer getMinimumTrlValueAllowed() { - return minimumTrlValueAllowed; - } + private List serviceProviders; - public void setMinimumTrlValueAllowed(Integer minimumTrlValueAllowed) { - this.minimumTrlValueAllowed = minimumTrlValueAllowed; - } + public List getCertificates() { + return certificates; + } - /** - * 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 List getServiceProviders() { + return serviceProviders; + } - public DccRevocation getDccRevocation() { - return this.dccRevocation; - } + @JsonProperty("certificates") + public void setCertificates(final List certificates) { + this.certificates = certificates; + } - public void setDccRevocation(DccRevocation dccRevocation) { - this.dccRevocation = dccRevocation; + @JsonProperty("serviceProviders") + public void setServiceProviders(final List serviceProviders) { + this.serviceProviders = serviceProviders; + } } - public int getInfectionThreshold() { - return infectionThreshold; - } + public static class Api { - public void setInfectionThreshold(int infectionThreshold) { - this.infectionThreshold = infectionThreshold; - } + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String versionPath; + @Pattern(regexp = VERSION_REGEX) + private String versionV1; + @Pattern(regexp = VERSION_REGEX) + private String versionV2; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String countryPath; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String originCountry; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String datePath; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String hourPath; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String diagnosisKeysPath; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String parametersPath; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String appConfigFileName; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String appConfigV2IosFileName; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String appConfigV2AndroidFileName; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String statisticsFileName; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String localStatisticsFileName; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String traceWarningsPath; - public static class StatisticsConfig { + public String getAppConfigFileName() { + return appConfigFileName; + } - private String statisticPath; + public String getAppConfigV2AndroidFileName() { + return appConfigV2AndroidFileName; + } - private String localStatisticPath; + public String getAppConfigV2IosFileName() { + return appConfigV2IosFileName; + } - private String accessKey; + public String getCountryPath() { + return countryPath; + } - private String secretKey; + public String getDatePath() { + return datePath; + } - private String endpoint; + public String getDiagnosisKeysPath() { + return diagnosisKeysPath; + } - private String bucket; + public String getHourPath() { + return hourPath; + } - private String pandemicRadarUrl; + public String getLocalStatisticsFileName() { + return localStatisticsFileName; + } - public String getPandemicRadarUrl() { - return pandemicRadarUrl; + public String getOriginCountry() { + return originCountry; } - public void setPandemicRadarUrl(String pandemicRadarUrl) { - this.pandemicRadarUrl = pandemicRadarUrl; + public String getParametersPath() { + return parametersPath; } - public String getLocalStatisticPath() { - return localStatisticPath; + public String getStatisticsFileName() { + return statisticsFileName; } - public void setLocalStatisticPath(String localStatisticPath) { - this.localStatisticPath = localStatisticPath; + public String getTraceWarningsPath() { + return traceWarningsPath; } - public String getStatisticPath() { - return statisticPath; + public String getVersionPath() { + return versionPath; } - public void setStatisticPath(String statisticPath) { - this.statisticPath = statisticPath; + public String getVersionV1() { + return versionV1; } - public String getAccessKey() { - return accessKey; + public String getVersionV2() { + return versionV2; } - public void setAccessKey(String accessKey) { - this.accessKey = accessKey; + public void setAppConfigFileName(final String appConfigFileName) { + this.appConfigFileName = appConfigFileName; } - public String getSecretKey() { - return secretKey; + public void setAppConfigV2AndroidFileName(final String appConfigV2AndroidFileName) { + this.appConfigV2AndroidFileName = appConfigV2AndroidFileName; } - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; + public void setAppConfigV2IosFileName(final String appConfigV2IosFileName) { + this.appConfigV2IosFileName = appConfigV2IosFileName; } - public String getEndpoint() { - return endpoint; + public void setCountryPath(final String countryPath) { + this.countryPath = countryPath; } - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; + public void setDatePath(final String datePath) { + this.datePath = datePath; } - public String getBucket() { - return bucket; + public void setDiagnosisKeysPath(final String diagnosisKeysPath) { + this.diagnosisKeysPath = diagnosisKeysPath; } - public void setBucket(String bucket) { - this.bucket = bucket; + public void setHourPath(final String hourPath) { + this.hourPath = hourPath; } - } - public static class TekExport { + public void setLocalStatisticsFileName(final String localStatisticsFileName) { + this.localStatisticsFileName = localStatisticsFileName; + } - @Pattern(regexp = FILE_NAME_WITH_TYPE_REGEX) - private String fileName; - @Pattern(regexp = CHAR_NUMBER_AND_SPACE_REGEX) - private String fileHeader; - @Min(0) - @Max(32) - private Integer fileHeaderWidth; + public void setOriginCountry(final String originCountry) { + this.originCountry = originCountry; + } - public String getFileName() { - return fileName; + public void setParametersPath(final String parametersPath) { + this.parametersPath = parametersPath; } - public void setFileName(String fileName) { - this.fileName = fileName; + public void setStatisticsFileName(final String statisticsFileName) { + this.statisticsFileName = statisticsFileName; } - public String getFileHeader() { - return fileHeader; + public void setTraceWarningsPath(final String traceWarningsPath) { + this.traceWarningsPath = traceWarningsPath; } - public void setFileHeader(String fileHeader) { - this.fileHeader = fileHeader; + public void setVersionPath(final String versionPath) { + this.versionPath = versionPath; } - public Integer getFileHeaderWidth() { - return fileHeaderWidth; + public void setVersionV1(final String versionV1) { + this.versionV1 = versionV1; } - public void setFileHeaderWidth(Integer fileHeaderWidth) { - this.fileHeaderWidth = fileHeaderWidth; + public void setVersionV2(final String versionV2) { + this.versionV2 = versionV2; } } - public static class TestData { + public static class AppConfigParameters { - private Integer seed; - private Integer exposuresPerHour; - private boolean distributionTestdataConsentToFederation; + public static class AndroidEventDrivenUserSurveyParameters extends CommonEdusParameters { - public Integer getSeed() { - return seed; - } + @NotNull + private Boolean requireBasicIntegrity; + @NotNull + private Boolean requireCtsProfileMatch; + @NotNull + private Boolean requireEvaluationTypeBasic; + @NotNull + private Boolean requireEvaluationTypeHardwareBacked; - public void setSeed(Integer seed) { - this.seed = seed; - } + public Boolean getRequireBasicIntegrity() { + return requireBasicIntegrity; + } - public Integer getExposuresPerHour() { - return exposuresPerHour; - } + public Boolean getRequireCtsProfileMatch() { + return requireCtsProfileMatch; + } - public void setExposuresPerHour(Integer exposuresPerHour) { - this.exposuresPerHour = exposuresPerHour; - } + public Boolean getRequireEvaluationTypeBasic() { + return requireEvaluationTypeBasic; + } - public boolean getDistributionTestdataConsentToFederation() { - return distributionTestdataConsentToFederation; - } + public Boolean getRequireEvaluationTypeHardwareBacked() { + return requireEvaluationTypeHardwareBacked; + } - public void setDistributionTestdataConsentToFederation(boolean distributionTestdataConsentToFederation) { - this.distributionTestdataConsentToFederation = distributionTestdataConsentToFederation; - } - } + public void setRequireBasicIntegrity(final Boolean requireBasicIntegrity) { + this.requireBasicIntegrity = requireBasicIntegrity; + } - public static class Paths { + public void setRequireCtsProfileMatch(final Boolean requireCtsProfileMatch) { + this.requireCtsProfileMatch = requireCtsProfileMatch; + } - @Pattern(regexp = PRIVATE_KEY_REGEX) - private String privateKey; - @Pattern(regexp = PATH_REGEX) - private String output; + public void setRequireEvaluationTypeBasic(final Boolean requireEvaluationTypeBasic) { + this.requireEvaluationTypeBasic = requireEvaluationTypeBasic; + } - public String getPrivateKey() { - return privateKey; + public void setRequireEvaluationTypeHardwareBacked(final Boolean requireEvaluationTypeHardwareBacked) { + this.requireEvaluationTypeHardwareBacked = requireEvaluationTypeHardwareBacked; + } } - public void setPrivateKey(String privateKey) { - this.privateKey = privateKey; - } + public static class AndroidExposureDetectionParameters { + private static final int LOWER_BOUNDARY_OVERALL_TIMEOUT = 0; - public String getOutput() { - return output; + public static final String MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = + "Android Exposure Detection: overall timeout in seconds must be greater than or equal to " + + LOWER_BOUNDARY_OVERALL_TIMEOUT; + private static final int UPPER_BOUNDARY_OVERALL_TIMEOUT = 3600; + public static final String MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = + "Android Exposure Detection: overall timeout in seconds must be lower than or equal to " + + UPPER_BOUNDARY_OVERALL_TIMEOUT; + private static final int LOWER_BOUNDARY_MAX_EXPOSURE_DETECTIONS = 0; + public static final String MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = + "Android Exposure Detection: max exposure detections per interval must be greater than or equal to " + + LOWER_BOUNDARY_MAX_EXPOSURE_DETECTIONS; + private static final int UPPER_BOUNDARY_MAX_EXPOSURE_DETECTIONS = 6; + public static final String MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = + "Android Exposure Detection: max exposure detections per interval must be lower than or equal to " + + UPPER_BOUNDARY_MAX_EXPOSURE_DETECTIONS; + @Min(value = LOWER_BOUNDARY_MAX_EXPOSURE_DETECTIONS, message = MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) + @Max(value = UPPER_BOUNDARY_MAX_EXPOSURE_DETECTIONS, message = MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) + private Integer maxExposureDetectionsPerInterval; + @Min(value = LOWER_BOUNDARY_OVERALL_TIMEOUT, message = MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) + @Max(value = UPPER_BOUNDARY_OVERALL_TIMEOUT, message = MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) + private Integer overallTimeoutInSeconds; + + public Integer getMaxExposureDetectionsPerInterval() { + return maxExposureDetectionsPerInterval; + } + + public Integer getOverallTimeoutInSeconds() { + return overallTimeoutInSeconds; + } + + public void setMaxExposureDetectionsPerInterval(final Integer maxExposureDetectionsPerInterval) { + this.maxExposureDetectionsPerInterval = maxExposureDetectionsPerInterval; + } + + public void setOverallTimeoutInSeconds(final Integer overallTimeoutInSeconds) { + this.overallTimeoutInSeconds = overallTimeoutInSeconds; + } } - public void setOutput(String output) { - this.output = output; + public static class AndroidKeyDownloadParameters extends CommonKeyDownloadParameters { + + private static final int LOWER_BOUNDARY_DOWNLOAD_TIMEOUT = 0; + public static final String MIN_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT = + "Download timeout in seconds must be greater than or equal to " + LOWER_BOUNDARY_DOWNLOAD_TIMEOUT; + private static final int UPPER_BOUNDARY_DOWNLOAD_TIMEOUT = 1800; + public static final String MAX_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT = + "Download timeout in seconds must be lower than or equal to " + UPPER_BOUNDARY_DOWNLOAD_TIMEOUT; + private static final int LOWER_BOUNDARY_OVERALL_TIMEOUT = 0; + public static final String MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = + "Android Key Download: overall timeout in seconds must be greater than or equal to " + + LOWER_BOUNDARY_OVERALL_TIMEOUT; + private static final int UPPER_BOUNDARY_OVERALL_TIMEOUT = 1800; + public static final String MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = + "Android Key Download: overall timeout in seconds must be lower than or equal to " + + UPPER_BOUNDARY_OVERALL_TIMEOUT; + + @Min(value = LOWER_BOUNDARY_DOWNLOAD_TIMEOUT, message = MIN_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT) + @Max(value = UPPER_BOUNDARY_DOWNLOAD_TIMEOUT, message = MAX_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT) + private Integer downloadTimeoutInSeconds; + @Min(value = LOWER_BOUNDARY_OVERALL_TIMEOUT, message = MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) + @Max(value = UPPER_BOUNDARY_OVERALL_TIMEOUT, message = MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) + private Integer overallTimeoutInSeconds; + + public Integer getDownloadTimeoutInSeconds() { + return downloadTimeoutInSeconds; + } + + public Integer getOverallTimeoutInSeconds() { + return overallTimeoutInSeconds; + } + + public void setDownloadTimeoutInSeconds(final Integer downloadTimeoutInSeconds) { + this.downloadTimeoutInSeconds = downloadTimeoutInSeconds; + } + + public void setOverallTimeoutInSeconds(final Integer overallTimeoutInSeconds) { + this.overallTimeoutInSeconds = overallTimeoutInSeconds; + } } - } - public static class Api { + public static class AndroidPrivacyPreservingAnalyticsParameters extends CommonPpaParameters { - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String versionPath; - @Pattern(regexp = VERSION_REGEX) - private String versionV1; - @Pattern(regexp = VERSION_REGEX) - private String versionV2; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String countryPath; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String originCountry; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String datePath; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String hourPath; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String diagnosisKeysPath; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String parametersPath; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String appConfigFileName; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String appConfigV2IosFileName; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String appConfigV2AndroidFileName; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String statisticsFileName; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String localStatisticsFileName; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String traceWarningsPath; + @NotNull + private Boolean requireBasicIntegrity; + @NotNull + private Boolean requireCtsProfileMatch; + @NotNull + private Boolean requireEvaluationTypeBasic; + @NotNull + private Boolean requireEvaluationTypeHardwareBacked; + + public Boolean getRequireBasicIntegrity() { + return requireBasicIntegrity; + } + + public Boolean getRequireCtsProfileMatch() { + return requireCtsProfileMatch; + } + + public Boolean getRequireEvaluationTypeBasic() { + return requireEvaluationTypeBasic; + } + + public Boolean getRequireEvaluationTypeHardwareBacked() { + return requireEvaluationTypeHardwareBacked; + } + + public void setRequireBasicIntegrity(final Boolean requireBasicIntegrity) { + this.requireBasicIntegrity = requireBasicIntegrity; + } - public String getStatisticsFileName() { - return statisticsFileName; - } + public void setRequireCtsProfileMatch(final Boolean requireCtsProfileMatch) { + this.requireCtsProfileMatch = requireCtsProfileMatch; + } - public void setStatisticsFileName(String statisticsFileName) { - this.statisticsFileName = statisticsFileName; - } + public void setRequireEvaluationTypeBasic(final Boolean requireEvaluationTypeBasic) { + this.requireEvaluationTypeBasic = requireEvaluationTypeBasic; + } - public String getLocalStatisticsFileName() { - return localStatisticsFileName; + public void setRequireEvaluationTypeHardwareBacked(final Boolean requireEvaluationTypeHardwareBacked) { + this.requireEvaluationTypeHardwareBacked = requireEvaluationTypeHardwareBacked; + } } - public void setLocalStatisticsFileName(String localStatisticsFileName) { - this.localStatisticsFileName = localStatisticsFileName; + public static class AndroidSrsPpacParameters extends AndroidPrivacyPreservingAnalyticsParameters { } - public String getAppConfigV2IosFileName() { - return appConfigV2IosFileName; - } + private static class CommonEdusParameters { - public void setAppConfigV2IosFileName(String appConfigV2IosFileName) { - this.appConfigV2IosFileName = appConfigV2IosFileName; - } + @Size(min = 1, max = 30) + private String otpQueryParameterName; + @NotNull + private Boolean surveyOnHighRiskEnabled; + @Pattern(regexp = URL_REGEX) + private String surveyOnHighRiskUrl; - public String getAppConfigV2AndroidFileName() { - return appConfigV2AndroidFileName; - } + public String getOtpQueryParameterName() { + return otpQueryParameterName; + } - public void setAppConfigV2AndroidFileName(String appConfigV2AndroidFileName) { - this.appConfigV2AndroidFileName = appConfigV2AndroidFileName; - } + public Boolean getSurveyOnHighRiskEnabled() { + return surveyOnHighRiskEnabled; + } - public String getVersionPath() { - return versionPath; - } + public String getSurveyOnHighRiskUrl() { + return surveyOnHighRiskUrl; + } - public void setVersionPath(String versionPath) { - this.versionPath = versionPath; - } + public void setOtpQueryParameterName(final String otpQueryParameterName) { + this.otpQueryParameterName = otpQueryParameterName; + } - public String getVersionV1() { - return versionV1; - } + public void setSurveyOnHighRiskEnabled(final Boolean surveyOnHighRiskEnabled) { + this.surveyOnHighRiskEnabled = surveyOnHighRiskEnabled; + } - public void setVersionV1(String versionV1) { - this.versionV1 = versionV1; + public void setSurveyOnHighRiskUrl(final String surveyOnHighRiskUrl) { + this.surveyOnHighRiskUrl = surveyOnHighRiskUrl; + } } - public String getVersionV2() { - return versionV2; - } + private abstract static class CommonKeyDownloadParameters { - public void setVersionV2(String versionV2) { - this.versionV2 = versionV2; - } + private String revokedDayPackages; + private String revokedHourPackages; - public String getCountryPath() { - return countryPath; - } + public List getRevokedDayPackages() { + return SerializationUtils.deserializeJson(revokedDayPackages, + typeFactory -> typeFactory.constructCollectionType(List.class, DeserializedDayPackageMetadata.class)); + } - public void setCountryPath(String countryPath) { - this.countryPath = countryPath; - } + public List getRevokedHourPackages() { + return SerializationUtils.deserializeJson(revokedHourPackages, + typeFactory -> typeFactory + .constructCollectionType(List.class, DeserializedHourPackageMetadata.class)); + } - public String getDatePath() { - return datePath; - } + public void setRevokedDayPackages(final String revokedDayPackages) { + this.revokedDayPackages = revokedDayPackages; + } - public void setDatePath(String datePath) { - this.datePath = datePath; + public void setRevokedHourPackages(final String revokedHourPackages) { + this.revokedHourPackages = revokedHourPackages; + } } - public String getHourPath() { - return hourPath; - } + private static class CommonPpaParameters { - public void setHourPath(String hourPath) { - this.hourPath = hourPath; - } + private Double probabilityToSubmit; + private Double probabilityToSubmitExposureWindows; + @PositiveOrZero + private Integer hoursSinceTestRegistrationToSubmitTestResultMetadata; + @PositiveOrZero + private Integer hoursSinceTestToSubmitKeySubmissionMetadata; - public String getDiagnosisKeysPath() { - return diagnosisKeysPath; - } + public Integer getHoursSinceTestRegistrationToSubmitTestResultMetadata() { + return hoursSinceTestRegistrationToSubmitTestResultMetadata; + } - public void setDiagnosisKeysPath(String diagnosisKeysPath) { - this.diagnosisKeysPath = diagnosisKeysPath; - } + public Integer getHoursSinceTestToSubmitKeySubmissionMetadata() { + return hoursSinceTestToSubmitKeySubmissionMetadata; + } - public String getParametersPath() { - return parametersPath; - } + public Double getProbabilityToSubmit() { + return probabilityToSubmit; + } - public void setParametersPath(String parametersPath) { - this.parametersPath = parametersPath; - } + public Double getProbabilityToSubmitExposureWindows() { + return probabilityToSubmitExposureWindows; + } - public String getAppConfigFileName() { - return appConfigFileName; - } + public void setHoursSinceTestRegistrationToSubmitTestResultMetadata(final Integer integer) { + this.hoursSinceTestRegistrationToSubmitTestResultMetadata = integer; + } - public void setAppConfigFileName(String appConfigFileName) { - this.appConfigFileName = appConfigFileName; - } + public void setHoursSinceTestToSubmitKeySubmissionMetadata( + final Integer hoursSinceTestToSubmitKeySubmissionMetadata) { + this.hoursSinceTestToSubmitKeySubmissionMetadata = hoursSinceTestToSubmitKeySubmissionMetadata; + } - public String getOriginCountry() { - return originCountry; - } + public void setProbabilityToSubmit(final Double probabilityToSubmit) { + this.probabilityToSubmit = probabilityToSubmit; + } - public void setOriginCountry(String originCountry) { - this.originCountry = originCountry; + public void setProbabilityToSubmitExposureWindows(final Double probabilityToSubmitExposureWindows) { + this.probabilityToSubmitExposureWindows = probabilityToSubmitExposureWindows; + } } - public String getTraceWarningsPath() { - return this.traceWarningsPath; - } + public static class DeserializedDayPackageMetadata { - public void setTraceWarningsPath(String traceWarningsPath) { - this.traceWarningsPath = traceWarningsPath; - } - } + private String region; + private String date; + private String etag; - public static class Signature { + public String getDate() { + return date; + } - @Pattern(regexp = BUNDLE_REGEX) - private String appBundleId; - private String androidPackage; - @Pattern(regexp = NUMBER_REGEX) - private String verificationKeyId; - @Pattern(regexp = VERSION_REGEX) - private String verificationKeyVersion; - @Pattern(regexp = ALGORITHM_OID_REGEX) - private String algorithmOid; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String algorithmName; - @Pattern(regexp = FILE_NAME_WITH_TYPE_REGEX) - private String fileName; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String securityProvider; + public String getEtag() { + return etag; + } - public String getAppBundleId() { - return appBundleId; + public String getRegion() { + return region; + } } - public void setAppBundleId(String appBundleId) { - this.appBundleId = appBundleId; - } + public static class DeserializedHourPackageMetadata extends DeserializedDayPackageMetadata { - public String getAndroidPackage() { - return androidPackage; - } + private Integer hour; - public void setAndroidPackage(String androidPackage) { - this.androidPackage = androidPackage; + public Integer getHour() { + return hour; + } } - public String getVerificationKeyId() { - return verificationKeyId; - } + public static class DgcParameters { - public void setVerificationKeyId(String verificationKeyId) { - this.verificationKeyId = verificationKeyId; - } + public static class DgcBlocklistParameters { - public String getVerificationKeyVersion() { - return verificationKeyVersion; - } + public static class DgcBlockedUvciChunk { - public void setVerificationKeyVersion(String verificationKeyVersion) { - this.verificationKeyVersion = verificationKeyVersion; - } + List indices; + String hash; + Integer validFrom; - public String getAlgorithmOid() { - return algorithmOid; - } + public byte[] getHash() { + return Hex.decode(hash); + } - public void setAlgorithmOid(String algorithmOid) { - this.algorithmOid = algorithmOid; - } + public List getIndices() { + return indices; + } - public String getAlgorithmName() { - return algorithmName; - } + public Integer getValidFrom() { + return validFrom; + } - public void setAlgorithmName(String algorithmName) { - this.algorithmName = algorithmName; - } + public void setHash(final String hash) { + this.hash = hash; + } - public String getFileName() { - return fileName; - } + public void setIndices(final List indices) { + this.indices = indices; + } - public void setFileName(String fileName) { - this.fileName = fileName; - } + public void setValidFrom(final Integer validFrom) { + this.validFrom = validFrom; + } + } - public String getSecurityProvider() { - return securityProvider; - } + private String blockedUvciChunks; + + /** + * Parse String from application.yaml parameter. + * + * @return parsed string in a list of DgcBlockedUvciChunk. + */ + public List getBlockedUvciChunks() { + return SerializationUtils.deserializeJson(blockedUvciChunks, + typeFactory -> typeFactory + .constructCollectionType(List.class, DgcBlockedUvciChunk.class)); + } + + public void setBlockedUvciChunks(final String blockedUvciChunks) { + this.blockedUvciChunks = blockedUvciChunks; + } + } + + public static class DgcTestCertificateParameters { + + @Min(0) + @Max(60) + private Integer waitAfterPublicKeyRegistrationInSeconds; + + @Min(0) + @Max(60) + private Integer waitForRetryInSeconds; - public void setSecurityProvider(String securityProvider) { - this.securityProvider = securityProvider; - } + public Integer getWaitAfterPublicKeyRegistrationInSeconds() { + return waitAfterPublicKeyRegistrationInSeconds; + } - /** - * Returns the static {@link SignatureInfo} configured in the application properties. - * - * @return SignatureInfo - */ - public SignatureInfo getSignatureInfo() { - return SignatureInfo.newBuilder() - .setAppBundleId(this.getAppBundleId()) - .setVerificationKeyVersion(this.getVerificationKeyVersion()) - .setVerificationKeyId(this.getVerificationKeyId()) - .setSignatureAlgorithm(this.getAlgorithmOid()) - .build(); - } - } + public Integer getWaitForRetryInSeconds() { + return waitForRetryInSeconds; + } - public static class QrCodePosterTemplate { + public void setWaitAfterPublicKeyRegistrationInSeconds(final Integer waitAfterPublicKeyRegistrationInSeconds) { + this.waitAfterPublicKeyRegistrationInSeconds = waitAfterPublicKeyRegistrationInSeconds; + } - private String template; - @NotNull - private Double offsetX; - @NotNull - private Double offsetY; - @NotNull - private Integer qrCodeSideLength; - @NotEmpty - private String publishedArchiveName; - private DescriptionTextBox descriptionTextBox; - private DescriptionTextBox addressTextBox; + public void setWaitForRetryInSeconds(final Integer waitForRetryInSeconds) { + this.waitForRetryInSeconds = waitForRetryInSeconds; + } + } + private DgcTestCertificateParameters dgcTestCertificateParameters; - public static class DescriptionTextBox { + @Min(0) + @Max(100) + private Integer expirationThresholdInDays; - @NotNull - private Double offsetX; - @NotNull - private Double offsetY; - @NotNull - private Integer width; - @NotNull - private Integer height; - @NotNull - private Integer fontSize; - @NotNull - private String fontColor; + private DgcBlocklistParameters blockListParameters; - public Double getOffsetX() { - return offsetX; - } + private String iosDgcReissueServicePublicKeyDigest; - public void setOffsetX(Double offsetX) { - this.offsetX = offsetX; - } + private String androidDgcReissueServicePublicKeyDigest; - public Double getOffsetY() { - return offsetY; + public byte[] getAndroidDgcReissueServicePublicKeyDigest() { + return Hex.decode(androidDgcReissueServicePublicKeyDigest); } - public void setOffsetY(Double offsetY) { - this.offsetY = offsetY; + public DgcBlocklistParameters getBlockListParameters() { + return blockListParameters; } - public Integer getWidth() { - return width; + public Integer getExpirationThresholdInDays() { + return expirationThresholdInDays; } - public void setWidth(Integer width) { - this.width = width; + public byte[] getIosDgcReissueServicePublicKeyDigest() { + return Hex.decode(iosDgcReissueServicePublicKeyDigest); } - public Integer getHeight() { - return height; + public DgcTestCertificateParameters getTestCertificateParameters() { + return dgcTestCertificateParameters; } - public void setHeight(Integer height) { - this.height = height; + public void setAndroidDgcReissueServicePublicKeyDigest(final String androidDgcReissueServicePublicKeyDigest) { + this.androidDgcReissueServicePublicKeyDigest = androidDgcReissueServicePublicKeyDigest; } - public Integer getFontSize() { - return fontSize; + public void setBlockListParameters(final DgcBlocklistParameters blockListParameters) { + this.blockListParameters = blockListParameters; } - public void setFontSize(Integer fontSize) { - this.fontSize = fontSize; + public void setExpirationThresholdInDays(final Integer expirationThresholdInDays) { + this.expirationThresholdInDays = expirationThresholdInDays; } - public String getFontColor() { - return fontColor; + public void setIosDgcReissueServicePublicKeyDigest(final String iosDgcReissueServicePublicKeyDigest) { + this.iosDgcReissueServicePublicKeyDigest = iosDgcReissueServicePublicKeyDigest; } - public void setFontColor(String fontColor) { - this.fontColor = fontColor; + public void setTestCertificateParameters(final DgcTestCertificateParameters dgcTestCertificateParameters) { + this.dgcTestCertificateParameters = dgcTestCertificateParameters; } } - public String getPublishedArchiveName() { - return publishedArchiveName; - } + public static class IosEventDrivenUserSurveyParameters extends CommonEdusParameters { - public void setPublishedArchiveName(String publishedArchiveName) { - this.publishedArchiveName = publishedArchiveName; } - public String getTemplate() { - return template; - } + public static class IosExposureDetectionParameters { - public void setTemplate(String template) { - this.template = template; - } + private static final int MIN_VALUE_MAX_EXPOSURE_DETECTIONS = 0; + public static final String MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = + "IOS Exposure Detection: max exposure detections per interval must be greater than or equal to " + + MIN_VALUE_MAX_EXPOSURE_DETECTIONS; + private static final int MAX_VALUE_MAX_EXPOSURE_DETECTIONS = 6; + public static final String MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = + "IOS Exposure Detection: max exposure detections per interval must be lower than or equal to " + + MAX_VALUE_MAX_EXPOSURE_DETECTIONS; - public Double getOffsetX() { - return offsetX; - } + @Min(value = MIN_VALUE_MAX_EXPOSURE_DETECTIONS, message = MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) + @Max(value = MAX_VALUE_MAX_EXPOSURE_DETECTIONS, message = MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) + private Integer maxExposureDetectionsPerInterval; - public void setOffsetX(Double offsetX) { - this.offsetX = offsetX; - } + public Integer getMaxExposureDetectionsPerInterval() { + return maxExposureDetectionsPerInterval; + } - public Double getOffsetY() { - return offsetY; - } + public void setMaxExposureDetectionsPerInterval(final Integer maxExposureDetectionsPerInterval) { + this.maxExposureDetectionsPerInterval = maxExposureDetectionsPerInterval; + } - public void setOffsetY(Double offsetY) { - this.offsetY = offsetY; } - public Integer getQrCodeSideLength() { - return qrCodeSideLength; - } + public static class IosKeyDownloadParameters extends CommonKeyDownloadParameters { - public void setQrCodeSideLength(Integer qrCodeSideLength) { - this.qrCodeSideLength = qrCodeSideLength; } - public DescriptionTextBox getDescriptionTextBox() { - return descriptionTextBox; - } + public static class IosPrivacyPreservingAnalyticsParameters extends CommonPpaParameters { - public void setDescriptionTextBox(DescriptionTextBox descriptionTextBox) { - this.descriptionTextBox = descriptionTextBox; } - public DescriptionTextBox getAddressTextBox() { - return addressTextBox; - } + private IosKeyDownloadParameters iosKeyDownloadParameters; - public void setAddressTextBox(DescriptionTextBox addressTextBox) { - this.addressTextBox = addressTextBox; - } - } + private AndroidKeyDownloadParameters androidKeyDownloadParameters; - public static class PresenceTracingParameters { + private IosExposureDetectionParameters iosExposureDetectionParameters; - private int qrCodeErrorCorrectionLevel; - private PlausibleDeniabilityParameters plausibleDeniabilityParameters; + private AndroidExposureDetectionParameters androidExposureDetectionParameters; - public static class PlausibleDeniabilityParameters { + private IosEventDrivenUserSurveyParameters iosEventDrivenUserSurveyParameters; - private double probabilityToFakeCheckInsIfNoCheckIns; - private double probabilityToFakeCheckInsIfSomeCheckIns; + private AndroidEventDrivenUserSurveyParameters androidEventDrivenUserSurveyParameters; - public double getProbabilityToFakeCheckInsIfNoCheckIns() { - return probabilityToFakeCheckInsIfNoCheckIns; - } + private IosPrivacyPreservingAnalyticsParameters iosPrivacyPreservingAnalyticsParameters; - public void setProbabilityToFakeCheckInsIfNoCheckIns(double probabilityToFakeCheckInsIfNoCheckIns) { - this.probabilityToFakeCheckInsIfNoCheckIns = probabilityToFakeCheckInsIfNoCheckIns; - } + private AndroidPrivacyPreservingAnalyticsParameters androidPrivacyPreservingAnalyticsParameters; - public double getProbabilityToFakeCheckInsIfSomeCheckIns() { - return probabilityToFakeCheckInsIfSomeCheckIns; - } + private AndroidSrsPpacParameters androidSrsPpacParameters; - public void setProbabilityToFakeCheckInsIfSomeCheckIns(double probabilityToFakeCheckInsIfSomeCheckIns) { - this.probabilityToFakeCheckInsIfSomeCheckIns = probabilityToFakeCheckInsIfSomeCheckIns; - } - } + private DgcParameters dgcParameters; - public PlausibleDeniabilityParameters getPlausibleDeniabilityParameters() { - return plausibleDeniabilityParameters; - } + @Min(1) + @Max(1000) + private int srsTimeSinceOnboardingInHours; - public void setPlausibleDeniabilityParameters(PlausibleDeniabilityParameters plausibleDeniabilityParameters) { - this.plausibleDeniabilityParameters = plausibleDeniabilityParameters; - } + @Min(1) + @Max(1000) + private int srsTimeBetweenSubmissionsInDays; - public int getQrCodeErrorCorrectionLevel() { - return qrCodeErrorCorrectionLevel; + public AndroidEventDrivenUserSurveyParameters getAndroidEventDrivenUserSurveyParameters() { + return androidEventDrivenUserSurveyParameters; } - public void setQrCodeErrorCorrectionLevel(int qrCodeErrorCorrectionLevel) { - this.qrCodeErrorCorrectionLevel = qrCodeErrorCorrectionLevel; + public AndroidExposureDetectionParameters getAndroidExposureDetectionParameters() { + return androidExposureDetectionParameters; } - } - - public PresenceTracingParameters getPresenceTracingParameters() { - return presenceTracingParameters; - } - - public void setPresenceTracingParameters( - PresenceTracingParameters presenceTracingParameters) { - this.presenceTracingParameters = presenceTracingParameters; - } - - public DigitalGreenCertificate getDigitalGreenCertificate() { - return digitalGreenCertificate; - } - - public void setDigitalGreenCertificate(DigitalGreenCertificate digitalGreenCertificate) { - this.digitalGreenCertificate = digitalGreenCertificate; - } - - public Integer getConnectionPoolSize() { - return connectionPoolSize; - } - - public void setConnectionPoolSize(Integer connectionPoolSize) { - this.connectionPoolSize = connectionPoolSize; - } - - public String getDefaultArchiveName() { - return defaultArchiveName; - } - public void setDefaultArchiveName(String defaultArchiveName) { - this.defaultArchiveName = defaultArchiveName; - } + public AndroidKeyDownloadParameters getAndroidKeyDownloadParameters() { + return androidKeyDownloadParameters; + } - public static class ObjectStore { + public AndroidPrivacyPreservingAnalyticsParameters getAndroidPrivacyPreservingAnalyticsParameters() { + return androidPrivacyPreservingAnalyticsParameters; + } - @Pattern(regexp = NO_WHITESPACE_REGEX) - private String accessKey; - @Pattern(regexp = NO_WHITESPACE_REGEX) - private String secretKey; - @Pattern(regexp = URL_REGEX) - private String endpoint; - @Min(1) - @Max(65535) - private Integer port; - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String bucket; - private Boolean setPublicReadAclOnPutObject; - @Min(1) - @Max(64) - private Integer maxNumberOfFailedOperations; - @Min(1) - @Max(64) - private Integer maxNumberOfS3Threads; - private Boolean forceUpdateKeyfiles; - @Max(Integer.MAX_VALUE) - private Integer hourFileRetentionDays; + public AndroidSrsPpacParameters getAndroidSrsPpacParameters() { + return androidSrsPpacParameters; + } - public String getAccessKey() { - return accessKey; + public DgcParameters getDgcParameters() { + return dgcParameters; } - public void setAccessKey(String accessKey) { - this.accessKey = accessKey; + public IosEventDrivenUserSurveyParameters getIosEventDrivenUserSurveyParameters() { + return iosEventDrivenUserSurveyParameters; } - public String getSecretKey() { - return secretKey; + public IosExposureDetectionParameters getIosExposureDetectionParameters() { + return iosExposureDetectionParameters; } - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; + public IosKeyDownloadParameters getIosKeyDownloadParameters() { + return iosKeyDownloadParameters; } - public String getEndpoint() { - return endpoint; + public IosPrivacyPreservingAnalyticsParameters getIosPrivacyPreservingAnalyticsParameters() { + return iosPrivacyPreservingAnalyticsParameters; } - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; + public int getSrsTimeBetweenSubmissionsInDays() { + return srsTimeBetweenSubmissionsInDays; } - public Integer getPort() { - return port; + public int getSrsTimeSinceOnboardingInHours() { + return srsTimeSinceOnboardingInHours; } - public void setPort(Integer port) { - this.port = port; + public void setAndroidEventDrivenUserSurveyParameters( + final AndroidEventDrivenUserSurveyParameters androidEventDrivenUserSurveyParameters) { + this.androidEventDrivenUserSurveyParameters = androidEventDrivenUserSurveyParameters; } - public String getBucket() { - return bucket; + public void setAndroidExposureDetectionParameters( + final AndroidExposureDetectionParameters androidExposureDetectionParameters) { + this.androidExposureDetectionParameters = androidExposureDetectionParameters; } - public void setBucket(String bucket) { - this.bucket = bucket; + public void setAndroidKeyDownloadParameters(final AndroidKeyDownloadParameters androidKeyDownloadParameters) { + this.androidKeyDownloadParameters = androidKeyDownloadParameters; } - public Boolean isSetPublicReadAclOnPutObject() { - return setPublicReadAclOnPutObject; + public void setAndroidPrivacyPreservingAnalyticsParameters( + final AndroidPrivacyPreservingAnalyticsParameters androidPrivacyPreservingAnalyticsParameters) { + this.androidPrivacyPreservingAnalyticsParameters = androidPrivacyPreservingAnalyticsParameters; } - public void setSetPublicReadAclOnPutObject(Boolean setPublicReadAclOnPutObject) { - this.setPublicReadAclOnPutObject = setPublicReadAclOnPutObject; + public void setAndroidSrsPpacParameters(final AndroidSrsPpacParameters androidSrsPpacParameters) { + this.androidSrsPpacParameters = androidSrsPpacParameters; } - public Integer getMaxNumberOfFailedOperations() { - return maxNumberOfFailedOperations; + public void setDgcParameters(final DgcParameters dgcParameters) { + this.dgcParameters = dgcParameters; } - public void setMaxNumberOfFailedOperations(Integer maxNumberOfFailedOperations) { - this.maxNumberOfFailedOperations = maxNumberOfFailedOperations; + public void setIosEventDrivenUserSurveyParameters( + final IosEventDrivenUserSurveyParameters iosEventDrivenUserSurveyParameters) { + this.iosEventDrivenUserSurveyParameters = iosEventDrivenUserSurveyParameters; } - public Integer getMaxNumberOfS3Threads() { - return maxNumberOfS3Threads; + public void setIosExposureDetectionParameters(final IosExposureDetectionParameters iosExposureDetectionParameters) { + this.iosExposureDetectionParameters = iosExposureDetectionParameters; } - public void setMaxNumberOfS3Threads(Integer maxNumberOfS3Threads) { - this.maxNumberOfS3Threads = maxNumberOfS3Threads; + public void setIosKeyDownloadParameters(final IosKeyDownloadParameters iosKeyDownloadParameters) { + this.iosKeyDownloadParameters = iosKeyDownloadParameters; } - public Boolean getForceUpdateKeyfiles() { - return forceUpdateKeyfiles; + public void setIosPrivacyPreservingAnalyticsParameters( + final IosPrivacyPreservingAnalyticsParameters iosPrivacyPreservingAnalyticsParameters) { + this.iosPrivacyPreservingAnalyticsParameters = iosPrivacyPreservingAnalyticsParameters; } - public void setForceUpdateKeyfiles(Boolean forceUpdateKeyfiles) { - this.forceUpdateKeyfiles = forceUpdateKeyfiles; + public void setSrsTimeBetweenSubmissionsInDays(final int srsTimeBetweenSubmissionsInDays) { + this.srsTimeBetweenSubmissionsInDays = srsTimeBetweenSubmissionsInDays; } - public Integer getHourFileRetentionDays() { - return hourFileRetentionDays; + public void setSrsTimeSinceOnboardingInHours(final int srsTimeSinceOnboardingInHours) { + this.srsTimeSinceOnboardingInHours = srsTimeSinceOnboardingInHours; } - public void setHourFileRetentionDays(Integer hourFileRetentionDays) { - this.hourFileRetentionDays = hourFileRetentionDays; - } } - public static class Client { + public static class AppFeature { - private String publicKey; + private String label; + private Integer value; - private String baseUrl; + public String getLabel() { + return label; + } - private Ssl ssl; + public Integer getValue() { + return value; + } - private int retryPeriod; + public void setLabel(final String label) { + this.label = label; + } - private int maxRetryPeriod; + public void setValue(final Integer value) { + this.value = value; + } + } - private int maxRetryAttempts; + public static class AppVersions { - public String getPublicKey() { - return publicKey; + private String latestIos; + private String minIos; + private String latestAndroid; + private String minAndroid; + @PositiveOrZero + private Integer latestAndroidVersionCode; + @PositiveOrZero + private Integer minAndroidVersionCode; + + public String getLatestAndroid() { + return latestAndroid; } - public void setPublicKey(String publicKey) { - this.publicKey = publicKey; + public Integer getLatestAndroidVersionCode() { + return latestAndroidVersionCode; } - public Ssl getSsl() { - return ssl; + public String getLatestIos() { + return latestIos; } - public void setSsl(Ssl ssl) { - this.ssl = ssl; + public String getMinAndroid() { + return minAndroid; } - public String getBaseUrl() { - return baseUrl; + public Integer getMinAndroidVersionCode() { + return minAndroidVersionCode; } - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; + public String getMinIos() { + return minIos; } - public int getRetryPeriod() { - return retryPeriod; + public void setLatestAndroid(final String latestAndroid) { + this.latestAndroid = latestAndroid; } - public void setRetryPeriod(int retryPeriod) { - this.retryPeriod = retryPeriod; + public void setLatestAndroidVersionCode(final Integer latestAndroidVersionCode) { + this.latestAndroidVersionCode = latestAndroidVersionCode; } - public int getMaxRetryPeriod() { - return maxRetryPeriod; + public void setLatestIos(final String latestIos) { + this.latestIos = latestIos; } - public void setMaxRetryPeriod(int maxRetryPeriod) { - this.maxRetryPeriod = maxRetryPeriod; + public void setMinAndroid(final String minAndroid) { + this.minAndroid = minAndroid; } - public int getMaxRetryAttempts() { - return maxRetryAttempts; + public void setMinAndroidVersionCode(final Integer minAndroidVersionCode) { + this.minAndroidVersionCode = minAndroidVersionCode; } - public void setMaxRetryAttempts(int maxRetryAttempts) { - this.maxRetryAttempts = maxRetryAttempts; + public void setMinIos(final String minIos) { + this.minIos = minIos; } + } + + public static class Client { public static class Ssl { @@ -1138,1053 +976,1249 @@ public File getTrustStore() { return trustStore; } - public void setTrustStore(File trustStore) { - this.trustStore = trustStore; - } - public String getTrustStorePassword() { return trustStorePassword; } - public void setTrustStorePassword(String trustStorePassword) { + public void setTrustStore(final File trustStore) { + this.trustStore = trustStore; + } + + public void setTrustStorePassword(final String trustStorePassword) { this.trustStorePassword = trustStorePassword; } } - } - public static class AppFeature { + private String publicKey; - private String label; - private Integer value; + private String baseUrl; - public String getLabel() { - return label; + private Ssl ssl; + + private int retryPeriod; + + private int maxRetryPeriod; + + private int maxRetryAttempts; + + public String getBaseUrl() { + return baseUrl; } - public void setLabel(String label) { - this.label = label; + public int getMaxRetryAttempts() { + return maxRetryAttempts; } - public Integer getValue() { - return value; + public int getMaxRetryPeriod() { + return maxRetryPeriod; } - public void setValue(Integer value) { - this.value = value; + public String getPublicKey() { + return publicKey; + } + + public int getRetryPeriod() { + return retryPeriod; + } + + public Ssl getSsl() { + return ssl; + } + + public void setBaseUrl(final String baseUrl) { + this.baseUrl = baseUrl; + } + + public void setMaxRetryAttempts(final int maxRetryAttempts) { + this.maxRetryAttempts = maxRetryAttempts; + } + + public void setMaxRetryPeriod(final int maxRetryPeriod) { + this.maxRetryPeriod = maxRetryPeriod; + } + + public void setPublicKey(final String publicKey) { + this.publicKey = publicKey; + } + + public void setRetryPeriod(final int retryPeriod) { + this.retryPeriod = retryPeriod; + } + + public void setSsl(final Ssl ssl) { + this.ssl = ssl; } } - public static class AppVersions { + public static class DccRevocation { - private String latestIos; - private String minIos; - private String latestAndroid; - private String minAndroid; - @PositiveOrZero - private Integer latestAndroidVersionCode; - @PositiveOrZero - private Integer minAndroidVersionCode; + private Client client; + private String certificate; + private String dccListPath; + private String dccRevocationDirectory; - public String getLatestIos() { - return latestIos; + public String getCertificate() { + return certificate; } - public void setLatestIos(String latestIos) { - this.latestIos = latestIos; + public Client getClient() { + return client; } - public String getMinIos() { - return minIos; + public String getDccListPath() { + return dccListPath; } - public void setMinIos(String minIos) { - this.minIos = minIos; + public String getDccRevocationDirectory() { + return dccRevocationDirectory; } - public String getLatestAndroid() { - return latestAndroid; + public void setCertificate(final String certificate) { + this.certificate = certificate; } - public void setLatestAndroid(String latestAndroid) { - this.latestAndroid = latestAndroid; + public void setClient(final Client client) { + this.client = client; } - public String getMinAndroid() { - return minAndroid; + public void setDccListPath(final String dccListPath) { + this.dccListPath = dccListPath; + } + + public void setDccRevocationDirectory(final String dccRevocationDirectory) { + this.dccRevocationDirectory = dccRevocationDirectory; } + } + + public static class DigitalGreenCertificate { + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String mahJsonPath; + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String prophylaxisJsonPath; + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String medicinalProductsJsonPath; + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String diseaseAgentTargetedJsonPath; + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String testManfJsonPath; + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String testResultJsonPath; + + @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) + private String testTypeJsonPath; + + @NotNull + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String dgcDirectory; + + @NotNull + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String boosterNotification; - public void setMinAndroid(String minAndroid) { - this.minAndroid = minAndroid; - } + @NotNull + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String valuesetsFileName; - public Integer getLatestAndroidVersionCode() { - return latestAndroidVersionCode; - } + private String cclDirectory; - public void setLatestAndroidVersionCode(Integer latestAndroidVersionCode) { - this.latestAndroidVersionCode = latestAndroidVersionCode; - } + private String[] cclAllowList; - public Integer getMinAndroidVersionCode() { - return minAndroidVersionCode; - } + private String allowList; - public void setMinAndroidVersionCode(Integer minAndroidVersionCode) { - this.minAndroidVersionCode = minAndroidVersionCode; - } - } + private String allowListSignature; - public static class AppConfigParameters { + private String allowListCertificate; - private IosKeyDownloadParameters iosKeyDownloadParameters; - private AndroidKeyDownloadParameters androidKeyDownloadParameters; - private IosExposureDetectionParameters iosExposureDetectionParameters; - private AndroidExposureDetectionParameters androidExposureDetectionParameters; - private IosEventDrivenUserSurveyParameters iosEventDrivenUserSurveyParameters; - private AndroidEventDrivenUserSurveyParameters androidEventDrivenUserSurveyParameters; - private IosPrivacyPreservingAnalyticsParameters iosPrivacyPreservingAnalyticsParameters; - private AndroidPrivacyPreservingAnalyticsParameters androidPrivacyPreservingAnalyticsParameters; - private AndroidSrsPpacParameters androidSrsPpacParameters; - private DgcParameters dgcParameters; + private String[] supportedLanguages; - @Min(1) - @Max(1000) - private int srsTimeSinceOnboardingInHours; - @Min(1) - @Max(1000) - private int srsTimeBetweenSubmissionsInDays; + private String exportArchiveName; - public IosEventDrivenUserSurveyParameters getIosEventDrivenUserSurveyParameters() { - return iosEventDrivenUserSurveyParameters; - } + private Client client; - public void setIosEventDrivenUserSurveyParameters( - IosEventDrivenUserSurveyParameters iosEventDrivenUserSurveyParameters) { - this.iosEventDrivenUserSurveyParameters = iosEventDrivenUserSurveyParameters; - } + private Client dscClient; - public AndroidEventDrivenUserSurveyParameters getAndroidEventDrivenUserSurveyParameters() { - return androidEventDrivenUserSurveyParameters; + /** + * getAllowList. + * + * @return AllowList + */ + public AllowList getAllowList() { + return SerializationUtils.deserializeJson(allowList, + typeFactory -> typeFactory + .constructType(AllowList.class)); } - public void setAndroidEventDrivenUserSurveyParameters( - AndroidEventDrivenUserSurveyParameters androidEventDrivenUserSurveyParameters) { - this.androidEventDrivenUserSurveyParameters = androidEventDrivenUserSurveyParameters; + public String getAllowListAsString() { + return allowList; } - public IosPrivacyPreservingAnalyticsParameters getIosPrivacyPreservingAnalyticsParameters() { - return iosPrivacyPreservingAnalyticsParameters; + public String getAllowListCertificate() { + return allowListCertificate; } - public void setIosPrivacyPreservingAnalyticsParameters( - IosPrivacyPreservingAnalyticsParameters iosPrivacyPreservingAnalyticsParameters) { - this.iosPrivacyPreservingAnalyticsParameters = iosPrivacyPreservingAnalyticsParameters; + public byte[] getAllowListSignature() { + return Hex.decode(allowListSignature); } - public AndroidPrivacyPreservingAnalyticsParameters getAndroidPrivacyPreservingAnalyticsParameters() { - return androidPrivacyPreservingAnalyticsParameters; + public String getBoosterNotification() { + return boosterNotification; } - public void setAndroidPrivacyPreservingAnalyticsParameters( - AndroidPrivacyPreservingAnalyticsParameters androidPrivacyPreservingAnalyticsParameters) { - this.androidPrivacyPreservingAnalyticsParameters = androidPrivacyPreservingAnalyticsParameters; + public String[] getCclAllowList() { + return cclAllowList; } - public IosKeyDownloadParameters getIosKeyDownloadParameters() { - return iosKeyDownloadParameters; + public String getCclDirectory() { + return cclDirectory; } - public void setIosKeyDownloadParameters(IosKeyDownloadParameters iosKeyDownloadParameters) { - this.iosKeyDownloadParameters = iosKeyDownloadParameters; + public Client getClient() { + return client; } - public AndroidKeyDownloadParameters getAndroidKeyDownloadParameters() { - return androidKeyDownloadParameters; + public String getDgcDirectory() { + return dgcDirectory; } - public void setAndroidKeyDownloadParameters(AndroidKeyDownloadParameters androidKeyDownloadParameters) { - this.androidKeyDownloadParameters = androidKeyDownloadParameters; + public String getDiseaseAgentTargetedJsonPath() { + return diseaseAgentTargetedJsonPath; } - public IosExposureDetectionParameters getIosExposureDetectionParameters() { - return iosExposureDetectionParameters; + public Client getDscClient() { + return dscClient; } - public void setIosExposureDetectionParameters(IosExposureDetectionParameters iosExposureDetectionParameters) { - this.iosExposureDetectionParameters = iosExposureDetectionParameters; + public String getExportArchiveName() { + return exportArchiveName; } - public AndroidExposureDetectionParameters getAndroidExposureDetectionParameters() { - return androidExposureDetectionParameters; + public String getMahJsonPath() { + return mahJsonPath; } - public void setAndroidExposureDetectionParameters( - AndroidExposureDetectionParameters androidExposureDetectionParameters) { - this.androidExposureDetectionParameters = androidExposureDetectionParameters; + public String getMedicinalProductsJsonPath() { + return medicinalProductsJsonPath; } - public DgcParameters getDgcParameters() { - return dgcParameters; + public String getProphylaxisJsonPath() { + return prophylaxisJsonPath; } - public void setDgcParameters(DgcParameters dgcParameters) { - this.dgcParameters = dgcParameters; + public String[] getSupportedLanguages() { + return supportedLanguages; } - public int getSrsTimeBetweenSubmissionsInDays() { - return srsTimeBetweenSubmissionsInDays; + public String getTestManfJsonPath() { + return testManfJsonPath; } - public int getSrsTimeSinceOnboardingInHours() { - return srsTimeSinceOnboardingInHours; + public String getTestResultJsonPath() { + return testResultJsonPath; } - public void setSrsTimeSinceOnboardingInHours(final int srsTimeSinceOnboardingInHours) { - this.srsTimeSinceOnboardingInHours = srsTimeSinceOnboardingInHours; + public String getTestTypeJsonPath() { + return testTypeJsonPath; } - public void setSrsTimeBetweenSubmissionsInDays(final int srsTimeBetweenSubmissionsInDays) { - this.srsTimeBetweenSubmissionsInDays = srsTimeBetweenSubmissionsInDays; + public String getValuesetsFileName() { + return valuesetsFileName; } - public AndroidSrsPpacParameters getAndroidSrsPpacParameters() { - return androidSrsPpacParameters; + public void setAllowList(final String allowList) { + this.allowList = allowList; } - public void setAndroidSrsPpacParameters(final AndroidSrsPpacParameters androidSrsPpacParameters) { - this.androidSrsPpacParameters = androidSrsPpacParameters; + public void setAllowListCertificate(final String allowListCertificate) { + this.allowListCertificate = allowListCertificate; } - public static class AndroidKeyDownloadParameters extends CommonKeyDownloadParameters { - - private static final int LOWER_BOUNDARY_DOWNLOAD_TIMEOUT = 0; - public static final String MIN_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT = - "Download timeout in seconds must be greater than or equal to " + LOWER_BOUNDARY_DOWNLOAD_TIMEOUT; - private static final int UPPER_BOUNDARY_DOWNLOAD_TIMEOUT = 1800; - public static final String MAX_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT = - "Download timeout in seconds must be lower than or equal to " + UPPER_BOUNDARY_DOWNLOAD_TIMEOUT; - private static final int LOWER_BOUNDARY_OVERALL_TIMEOUT = 0; - public static final String MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = - "Android Key Download: overall timeout in seconds must be greater than or equal to " - + LOWER_BOUNDARY_OVERALL_TIMEOUT; - private static final int UPPER_BOUNDARY_OVERALL_TIMEOUT = 1800; - public static final String MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = - "Android Key Download: overall timeout in seconds must be lower than or equal to " - + UPPER_BOUNDARY_OVERALL_TIMEOUT; - - @Min(value = LOWER_BOUNDARY_DOWNLOAD_TIMEOUT, message = MIN_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT) - @Max(value = UPPER_BOUNDARY_DOWNLOAD_TIMEOUT, message = MAX_VALUE_ERROR_MESSAGE_DOWNLOAD_TIMEOUT) - private Integer downloadTimeoutInSeconds; - @Min(value = LOWER_BOUNDARY_OVERALL_TIMEOUT, message = MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) - @Max(value = UPPER_BOUNDARY_OVERALL_TIMEOUT, message = MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) - private Integer overallTimeoutInSeconds; - - public Integer getDownloadTimeoutInSeconds() { - return downloadTimeoutInSeconds; - } - - public void setDownloadTimeoutInSeconds(Integer downloadTimeoutInSeconds) { - this.downloadTimeoutInSeconds = downloadTimeoutInSeconds; - } - - public Integer getOverallTimeoutInSeconds() { - return overallTimeoutInSeconds; - } - - public void setOverallTimeoutInSeconds(Integer overallTimeoutInSeconds) { - this.overallTimeoutInSeconds = overallTimeoutInSeconds; - } + public void setAllowListSignature(final String allowListSignature) { + this.allowListSignature = allowListSignature; } - public static class DeserializedDayPackageMetadata { - - private String region; - private String date; - private String etag; - - public String getRegion() { - return region; - } - - public String getDate() { - return date; - } - - public String getEtag() { - return etag; - } + public void setBoosterNotification(final String boosterNotification) { + this.boosterNotification = boosterNotification; } - public static class DeserializedHourPackageMetadata extends DeserializedDayPackageMetadata { - - private Integer hour; - - public Integer getHour() { - return hour; - } + public void setCclAllowList(final String[] cclAllowList) { + this.cclAllowList = cclAllowList; } - private abstract static class CommonKeyDownloadParameters { - - private String revokedDayPackages; - private String revokedHourPackages; - - public List getRevokedDayPackages() { - return SerializationUtils.deserializeJson(revokedDayPackages, - typeFactory -> typeFactory.constructCollectionType(List.class, DeserializedDayPackageMetadata.class)); - } - - public void setRevokedDayPackages(String revokedDayPackages) { - this.revokedDayPackages = revokedDayPackages; - } - - public List getRevokedHourPackages() { - return SerializationUtils.deserializeJson(revokedHourPackages, - typeFactory -> typeFactory - .constructCollectionType(List.class, DeserializedHourPackageMetadata.class)); - } - - public void setRevokedHourPackages(String revokedHourPackages) { - this.revokedHourPackages = revokedHourPackages; - } + public void setCclDirectory(final String cclDirectory) { + this.cclDirectory = cclDirectory; } - public static class IosKeyDownloadParameters extends CommonKeyDownloadParameters { - + public void setClient(final Client client) { + this.client = client; } - public static class IosExposureDetectionParameters { - - private static final int MIN_VALUE_MAX_EXPOSURE_DETECTIONS = 0; - public static final String MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = - "IOS Exposure Detection: max exposure detections per interval must be greater than or equal to " - + MIN_VALUE_MAX_EXPOSURE_DETECTIONS; - private static final int MAX_VALUE_MAX_EXPOSURE_DETECTIONS = 6; - public static final String MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = - "IOS Exposure Detection: max exposure detections per interval must be lower than or equal to " - + MAX_VALUE_MAX_EXPOSURE_DETECTIONS; - - @Min(value = MIN_VALUE_MAX_EXPOSURE_DETECTIONS, message = MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) - @Max(value = MAX_VALUE_MAX_EXPOSURE_DETECTIONS, message = MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) - private Integer maxExposureDetectionsPerInterval; - - public Integer getMaxExposureDetectionsPerInterval() { - return maxExposureDetectionsPerInterval; - } - - public void setMaxExposureDetectionsPerInterval(Integer maxExposureDetectionsPerInterval) { - this.maxExposureDetectionsPerInterval = maxExposureDetectionsPerInterval; - } - + public void setDgcDirectory(final String dgcDirectory) { + this.dgcDirectory = dgcDirectory; } - public static class AndroidExposureDetectionParameters { - - private static final int LOWER_BOUNDARY_OVERALL_TIMEOUT = 0; - public static final String MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = - "Android Exposure Detection: overall timeout in seconds must be greater than or equal to " - + LOWER_BOUNDARY_OVERALL_TIMEOUT; - private static final int UPPER_BOUNDARY_OVERALL_TIMEOUT = 3600; - public static final String MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT = - "Android Exposure Detection: overall timeout in seconds must be lower than or equal to " - + UPPER_BOUNDARY_OVERALL_TIMEOUT; - private static final int LOWER_BOUNDARY_MAX_EXPOSURE_DETECTIONS = 0; - public static final String MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = - "Android Exposure Detection: max exposure detections per interval must be greater than or equal to " - + LOWER_BOUNDARY_MAX_EXPOSURE_DETECTIONS; - private static final int UPPER_BOUNDARY_MAX_EXPOSURE_DETECTIONS = 6; - public static final String MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS = - "Android Exposure Detection: max exposure detections per interval must be lower than or equal to " - + UPPER_BOUNDARY_MAX_EXPOSURE_DETECTIONS; - @Min(value = LOWER_BOUNDARY_MAX_EXPOSURE_DETECTIONS, message = MIN_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) - @Max(value = UPPER_BOUNDARY_MAX_EXPOSURE_DETECTIONS, message = MAX_VALUE_ERROR_MESSAGE_MAX_EXPOSURE_DETECTIONS) - private Integer maxExposureDetectionsPerInterval; - @Min(value = LOWER_BOUNDARY_OVERALL_TIMEOUT, message = MIN_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) - @Max(value = UPPER_BOUNDARY_OVERALL_TIMEOUT, message = MAX_VALUE_ERROR_MESSAGE_OVERALL_TIMEOUT) - private Integer overallTimeoutInSeconds; + public void setDiseaseAgentTargetedJsonPath(final String diseaseAgentTargetedJsonPath) { + this.diseaseAgentTargetedJsonPath = diseaseAgentTargetedJsonPath; + } - public Integer getMaxExposureDetectionsPerInterval() { - return maxExposureDetectionsPerInterval; - } + public void setDscClient(final Client dscClient) { + this.dscClient = dscClient; + } - public void setMaxExposureDetectionsPerInterval(Integer maxExposureDetectionsPerInterval) { - this.maxExposureDetectionsPerInterval = maxExposureDetectionsPerInterval; - } + public void setExportArchiveName(final String exportArchiveName) { + this.exportArchiveName = exportArchiveName; + } - public Integer getOverallTimeoutInSeconds() { - return overallTimeoutInSeconds; - } + public void setMahJsonPath(final String mahJsonPath) { + this.mahJsonPath = mahJsonPath; + } - public void setOverallTimeoutInSeconds(Integer overallTimeoutInSeconds) { - this.overallTimeoutInSeconds = overallTimeoutInSeconds; - } + public void setMedicinalProductsJsonPath(final String medicinalProductsJsonPath) { + this.medicinalProductsJsonPath = medicinalProductsJsonPath; } - public static class IosEventDrivenUserSurveyParameters extends CommonEdusParameters { + public void setProphylaxisJsonPath(final String prophylaxisJsonPath) { + this.prophylaxisJsonPath = prophylaxisJsonPath; + } + public void setSupportedLanguages(final String[] supportedLanguages) { + this.supportedLanguages = supportedLanguages; } - private static class CommonEdusParameters { + public void setTestManfJsonPath(final String testManfJsonPath) { + this.testManfJsonPath = testManfJsonPath; + } - @Size(min = 1, max = 30) - private String otpQueryParameterName; - @NotNull - private Boolean surveyOnHighRiskEnabled; - @Pattern(regexp = URL_REGEX) - private String surveyOnHighRiskUrl; + public void setTestResultJsonPath(final String testResultJsonPath) { + this.testResultJsonPath = testResultJsonPath; + } - public String getOtpQueryParameterName() { - return otpQueryParameterName; - } + public void setTestTypeJsonPath(final String testTypeJsonPath) { + this.testTypeJsonPath = testTypeJsonPath; + } - public void setOtpQueryParameterName(String otpQueryParameterName) { - this.otpQueryParameterName = otpQueryParameterName; - } + public void setValuesetsFileName(final String valuesetsFileName) { + this.valuesetsFileName = valuesetsFileName; + } + } - public Boolean getSurveyOnHighRiskEnabled() { - return surveyOnHighRiskEnabled; - } + public static class ObjectStore { - public void setSurveyOnHighRiskEnabled(Boolean surveyOnHighRiskEnabled) { - this.surveyOnHighRiskEnabled = surveyOnHighRiskEnabled; - } + @Pattern(regexp = NO_WHITESPACE_REGEX) + private String accessKey; + @Pattern(regexp = NO_WHITESPACE_REGEX) + private String secretKey; + @Pattern(regexp = URL_REGEX) + private String endpoint; + @Min(1) + @Max(65535) + private Integer port; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String bucket; + private Boolean setPublicReadAclOnPutObject; + @Min(1) + @Max(64) + private Integer maxNumberOfFailedOperations; + @Min(1) + @Max(64) + private Integer maxNumberOfS3Threads; + private Boolean forceUpdateKeyfiles; + @Max(Integer.MAX_VALUE) + private Integer hourFileRetentionDays; - public String getSurveyOnHighRiskUrl() { - return surveyOnHighRiskUrl; - } + public String getAccessKey() { + return accessKey; + } - public void setSurveyOnHighRiskUrl(String surveyOnHighRiskUrl) { - this.surveyOnHighRiskUrl = surveyOnHighRiskUrl; - } + public String getBucket() { + return bucket; } - public static class AndroidEventDrivenUserSurveyParameters extends CommonEdusParameters { + public String getEndpoint() { + return endpoint; + } - @NotNull - private Boolean requireBasicIntegrity; - @NotNull - private Boolean requireCtsProfileMatch; - @NotNull - private Boolean requireEvaluationTypeBasic; - @NotNull - private Boolean requireEvaluationTypeHardwareBacked; + public Boolean getForceUpdateKeyfiles() { + return forceUpdateKeyfiles; + } - public Boolean getRequireBasicIntegrity() { - return requireBasicIntegrity; - } + public Integer getHourFileRetentionDays() { + return hourFileRetentionDays; + } - public void setRequireBasicIntegrity(Boolean requireBasicIntegrity) { - this.requireBasicIntegrity = requireBasicIntegrity; - } + public Integer getMaxNumberOfFailedOperations() { + return maxNumberOfFailedOperations; + } - public Boolean getRequireCtsProfileMatch() { - return requireCtsProfileMatch; - } + public Integer getMaxNumberOfS3Threads() { + return maxNumberOfS3Threads; + } - public void setRequireCtsProfileMatch(Boolean requireCtsProfileMatch) { - this.requireCtsProfileMatch = requireCtsProfileMatch; - } + public Integer getPort() { + return port; + } - public Boolean getRequireEvaluationTypeBasic() { - return requireEvaluationTypeBasic; - } + public String getSecretKey() { + return secretKey; + } - public void setRequireEvaluationTypeBasic(Boolean requireEvaluationTypeBasic) { - this.requireEvaluationTypeBasic = requireEvaluationTypeBasic; - } + public Boolean isSetPublicReadAclOnPutObject() { + return setPublicReadAclOnPutObject; + } - public Boolean getRequireEvaluationTypeHardwareBacked() { - return requireEvaluationTypeHardwareBacked; - } + public void setAccessKey(final String accessKey) { + this.accessKey = accessKey; + } - public void setRequireEvaluationTypeHardwareBacked(Boolean requireEvaluationTypeHardwareBacked) { - this.requireEvaluationTypeHardwareBacked = requireEvaluationTypeHardwareBacked; - } + public void setBucket(final String bucket) { + this.bucket = bucket; } - public static class IosPrivacyPreservingAnalyticsParameters extends CommonPpaParameters { + public void setEndpoint(final String endpoint) { + this.endpoint = endpoint; + } + public void setForceUpdateKeyfiles(final Boolean forceUpdateKeyfiles) { + this.forceUpdateKeyfiles = forceUpdateKeyfiles; } - private static class CommonPpaParameters { + public void setHourFileRetentionDays(final Integer hourFileRetentionDays) { + this.hourFileRetentionDays = hourFileRetentionDays; + } - private Double probabilityToSubmit; - private Double probabilityToSubmitExposureWindows; - @PositiveOrZero - private Integer hoursSinceTestRegistrationToSubmitTestResultMetadata; - @PositiveOrZero - private Integer hoursSinceTestToSubmitKeySubmissionMetadata; + public void setMaxNumberOfFailedOperations(final Integer maxNumberOfFailedOperations) { + this.maxNumberOfFailedOperations = maxNumberOfFailedOperations; + } - public Double getProbabilityToSubmit() { - return probabilityToSubmit; - } + public void setMaxNumberOfS3Threads(final Integer maxNumberOfS3Threads) { + this.maxNumberOfS3Threads = maxNumberOfS3Threads; + } - public void setProbabilityToSubmit(Double probabilityToSubmit) { - this.probabilityToSubmit = probabilityToSubmit; - } + public void setPort(final Integer port) { + this.port = port; + } - public Double getProbabilityToSubmitExposureWindows() { - return probabilityToSubmitExposureWindows; - } + public void setSecretKey(final String secretKey) { + this.secretKey = secretKey; + } - public void setProbabilityToSubmitExposureWindows(Double probabilityToSubmitExposureWindows) { - this.probabilityToSubmitExposureWindows = probabilityToSubmitExposureWindows; - } + public void setSetPublicReadAclOnPutObject(final Boolean setPublicReadAclOnPutObject) { + this.setPublicReadAclOnPutObject = setPublicReadAclOnPutObject; + } + } - public Integer getHoursSinceTestRegistrationToSubmitTestResultMetadata() { - return hoursSinceTestRegistrationToSubmitTestResultMetadata; - } + public static class Paths { - public void setHoursSinceTestRegistrationToSubmitTestResultMetadata( - Integer hoursSinceTestRegistrationToSubmitTestResultMetadata) { - this.hoursSinceTestRegistrationToSubmitTestResultMetadata = - hoursSinceTestRegistrationToSubmitTestResultMetadata; - } + @Pattern(regexp = PRIVATE_KEY_REGEX) + private String privateKey; + @Pattern(regexp = PATH_REGEX) + private String output; - public Integer getHoursSinceTestToSubmitKeySubmissionMetadata() { - return hoursSinceTestToSubmitKeySubmissionMetadata; - } + public String getOutput() { + return output; + } - public void setHoursSinceTestToSubmitKeySubmissionMetadata(Integer hoursSinceTestToSubmitKeySubmissionMetadata) { - this.hoursSinceTestToSubmitKeySubmissionMetadata = hoursSinceTestToSubmitKeySubmissionMetadata; - } + public String getPrivateKey() { + return privateKey; } - public static class AndroidPrivacyPreservingAnalyticsParameters extends CommonPpaParameters { + public void setOutput(final String output) { + this.output = output; + } - @NotNull - private Boolean requireBasicIntegrity; - @NotNull - private Boolean requireCtsProfileMatch; - @NotNull - private Boolean requireEvaluationTypeBasic; - @NotNull - private Boolean requireEvaluationTypeHardwareBacked; + public void setPrivateKey(final String privateKey) { + this.privateKey = privateKey; + } + } - public Boolean getRequireBasicIntegrity() { - return requireBasicIntegrity; - } + public static class PresenceTracingParameters { - public void setRequireBasicIntegrity(Boolean requireBasicIntegrity) { - this.requireBasicIntegrity = requireBasicIntegrity; - } + public static class PlausibleDeniabilityParameters { - public Boolean getRequireCtsProfileMatch() { - return requireCtsProfileMatch; - } + private double probabilityToFakeCheckInsIfNoCheckIns; + private double probabilityToFakeCheckInsIfSomeCheckIns; - public void setRequireCtsProfileMatch(Boolean requireCtsProfileMatch) { - this.requireCtsProfileMatch = requireCtsProfileMatch; + public double getProbabilityToFakeCheckInsIfNoCheckIns() { + return probabilityToFakeCheckInsIfNoCheckIns; } - public Boolean getRequireEvaluationTypeBasic() { - return requireEvaluationTypeBasic; + public double getProbabilityToFakeCheckInsIfSomeCheckIns() { + return probabilityToFakeCheckInsIfSomeCheckIns; } - public void setRequireEvaluationTypeBasic(Boolean requireEvaluationTypeBasic) { - this.requireEvaluationTypeBasic = requireEvaluationTypeBasic; + public void setProbabilityToFakeCheckInsIfNoCheckIns(final double probabilityToFakeCheckInsIfNoCheckIns) { + this.probabilityToFakeCheckInsIfNoCheckIns = probabilityToFakeCheckInsIfNoCheckIns; } - public Boolean getRequireEvaluationTypeHardwareBacked() { - return requireEvaluationTypeHardwareBacked; + public void setProbabilityToFakeCheckInsIfSomeCheckIns(final double probabilityToFakeCheckInsIfSomeCheckIns) { + this.probabilityToFakeCheckInsIfSomeCheckIns = probabilityToFakeCheckInsIfSomeCheckIns; } + } - public void setRequireEvaluationTypeHardwareBacked(Boolean requireEvaluationTypeHardwareBacked) { - this.requireEvaluationTypeHardwareBacked = requireEvaluationTypeHardwareBacked; - } + private int qrCodeErrorCorrectionLevel; + + private PlausibleDeniabilityParameters plausibleDeniabilityParameters; + + public PlausibleDeniabilityParameters getPlausibleDeniabilityParameters() { + return plausibleDeniabilityParameters; } - public static class AndroidSrsPpacParameters extends AndroidPrivacyPreservingAnalyticsParameters { + public int getQrCodeErrorCorrectionLevel() { + return qrCodeErrorCorrectionLevel; } - public static class DgcParameters { + public void setPlausibleDeniabilityParameters(final PlausibleDeniabilityParameters plausibleDeniabilityParameters) { + this.plausibleDeniabilityParameters = plausibleDeniabilityParameters; + } - private DgcTestCertificateParameters dgcTestCertificateParameters; + public void setQrCodeErrorCorrectionLevel(final int qrCodeErrorCorrectionLevel) { + this.qrCodeErrorCorrectionLevel = qrCodeErrorCorrectionLevel; + } + } - @Min(0) - @Max(100) - private Integer expirationThresholdInDays; + public static class QrCodePosterTemplate { - private DgcBlocklistParameters blockListParameters; + public static class DescriptionTextBox { + + @NotNull + private Double offsetX; + @NotNull + private Double offsetY; + @NotNull + private Integer width; + @NotNull + private Integer height; + @NotNull + private Integer fontSize; + @NotNull + private String fontColor; - private String iosDgcReissueServicePublicKeyDigest; + public String getFontColor() { + return fontColor; + } - private String androidDgcReissueServicePublicKeyDigest; + public Integer getFontSize() { + return fontSize; + } - public DgcTestCertificateParameters getTestCertificateParameters() { - return dgcTestCertificateParameters; + public Integer getHeight() { + return height; } - public void setTestCertificateParameters(DgcTestCertificateParameters dgcTestCertificateParameters) { - this.dgcTestCertificateParameters = dgcTestCertificateParameters; + public Double getOffsetX() { + return offsetX; } - public Integer getExpirationThresholdInDays() { - return expirationThresholdInDays; + public Double getOffsetY() { + return offsetY; } - public void setExpirationThresholdInDays(Integer expirationThresholdInDays) { - this.expirationThresholdInDays = expirationThresholdInDays; + public Integer getWidth() { + return width; } - public DgcBlocklistParameters getBlockListParameters() { - return blockListParameters; + public void setFontColor(final String fontColor) { + this.fontColor = fontColor; } - public void setBlockListParameters(DgcBlocklistParameters blockListParameters) { - this.blockListParameters = blockListParameters; + public void setFontSize(final Integer fontSize) { + this.fontSize = fontSize; } - public byte[] getIosDgcReissueServicePublicKeyDigest() { - return Hex.decode(iosDgcReissueServicePublicKeyDigest); + public void setHeight(final Integer height) { + this.height = height; } - public void setIosDgcReissueServicePublicKeyDigest(String iosDgcReissueServicePublicKeyDigest) { - this.iosDgcReissueServicePublicKeyDigest = iosDgcReissueServicePublicKeyDigest; + public void setOffsetX(final Double offsetX) { + this.offsetX = offsetX; } - public byte[] getAndroidDgcReissueServicePublicKeyDigest() { - return Hex.decode(androidDgcReissueServicePublicKeyDigest); + public void setOffsetY(final Double offsetY) { + this.offsetY = offsetY; } - public void setAndroidDgcReissueServicePublicKeyDigest(String androidDgcReissueServicePublicKeyDigest) { - this.androidDgcReissueServicePublicKeyDigest = androidDgcReissueServicePublicKeyDigest; + public void setWidth(final Integer width) { + this.width = width; } + } - public static class DgcTestCertificateParameters { + private String template; + @NotNull + private Double offsetX; + @NotNull + private Double offsetY; + @NotNull + private Integer qrCodeSideLength; + @NotEmpty + private String publishedArchiveName; + private DescriptionTextBox descriptionTextBox; - @Min(0) - @Max(60) - private Integer waitAfterPublicKeyRegistrationInSeconds; + private DescriptionTextBox addressTextBox; - @Min(0) - @Max(60) - private Integer waitForRetryInSeconds; + public DescriptionTextBox getAddressTextBox() { + return addressTextBox; + } - public Integer getWaitAfterPublicKeyRegistrationInSeconds() { - return waitAfterPublicKeyRegistrationInSeconds; - } + public DescriptionTextBox getDescriptionTextBox() { + return descriptionTextBox; + } - public void setWaitAfterPublicKeyRegistrationInSeconds(Integer waitAfterPublicKeyRegistrationInSeconds) { - this.waitAfterPublicKeyRegistrationInSeconds = waitAfterPublicKeyRegistrationInSeconds; - } + public Double getOffsetX() { + return offsetX; + } - public Integer getWaitForRetryInSeconds() { - return waitForRetryInSeconds; - } + public Double getOffsetY() { + return offsetY; + } - public void setWaitForRetryInSeconds(Integer waitForRetryInSeconds) { - this.waitForRetryInSeconds = waitForRetryInSeconds; - } - } + public String getPublishedArchiveName() { + return publishedArchiveName; + } - public static class DgcBlocklistParameters { + public Integer getQrCodeSideLength() { + return qrCodeSideLength; + } - private String blockedUvciChunks; + public String getTemplate() { + return template; + } - /** - * Parse String from application.yaml parameter. - * - * @return parsed string in a list of DgcBlockedUvciChunk. - */ - public List getBlockedUvciChunks() { - return SerializationUtils.deserializeJson(blockedUvciChunks, - typeFactory -> typeFactory - .constructCollectionType(List.class, DgcBlockedUvciChunk.class)); - } + public void setAddressTextBox(final DescriptionTextBox addressTextBox) { + this.addressTextBox = addressTextBox; + } - public void setBlockedUvciChunks(String blockedUvciChunks) { - this.blockedUvciChunks = blockedUvciChunks; - } + public void setDescriptionTextBox(final DescriptionTextBox descriptionTextBox) { + this.descriptionTextBox = descriptionTextBox; + } - public static class DgcBlockedUvciChunk { + public void setOffsetX(final Double offsetX) { + this.offsetX = offsetX; + } - List indices; - String hash; - Integer validFrom; + public void setOffsetY(final Double offsetY) { + this.offsetY = offsetY; + } - public List getIndices() { - return indices; - } + public void setPublishedArchiveName(final String publishedArchiveName) { + this.publishedArchiveName = publishedArchiveName; + } - public void setIndices(List indices) { - this.indices = indices; - } + public void setQrCodeSideLength(final Integer qrCodeSideLength) { + this.qrCodeSideLength = qrCodeSideLength; + } - public byte[] getHash() { - return Hex.decode(hash); - } + public void setTemplate(final String template) { + this.template = template; + } + } - public void setHash(String hash) { - this.hash = hash; - } + public static class Signature { - public Integer getValidFrom() { - return validFrom; - } + @Pattern(regexp = BUNDLE_REGEX) + private String appBundleId; + private String androidPackage; + @Pattern(regexp = NUMBER_REGEX) + private String verificationKeyId; + @Pattern(regexp = VERSION_REGEX) + private String verificationKeyVersion; + @Pattern(regexp = ALGORITHM_OID_REGEX) + private String algorithmOid; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String algorithmName; + @Pattern(regexp = FILE_NAME_WITH_TYPE_REGEX) + private String fileName; + @Pattern(regexp = CHAR_AND_NUMBER_REGEX) + private String securityProvider; - public void setValidFrom(Integer validFrom) { - this.validFrom = validFrom; - } - } - } + public String getAlgorithmName() { + return algorithmName; } + public String getAlgorithmOid() { + return algorithmOid; + } - } + public String getAndroidPackage() { + return androidPackage; + } - public static class DigitalGreenCertificate { + public String getAppBundleId() { + return appBundleId; + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String mahJsonPath; + public String getFileName() { + return fileName; + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String prophylaxisJsonPath; + public String getSecurityProvider() { + return securityProvider; + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String medicinalProductsJsonPath; + /** + * Returns the static {@link SignatureInfo} configured in the application properties. + * + * @return SignatureInfo + */ + public SignatureInfo getSignatureInfo() { + return SignatureInfo.newBuilder() + .setAppBundleId(getAppBundleId()) + .setVerificationKeyVersion(getVerificationKeyVersion()) + .setVerificationKeyId(getVerificationKeyId()) + .setSignatureAlgorithm(getAlgorithmOid()) + .build(); + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String diseaseAgentTargetedJsonPath; + public String getVerificationKeyId() { + return verificationKeyId; + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String testManfJsonPath; + public String getVerificationKeyVersion() { + return verificationKeyVersion; + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String testResultJsonPath; + public void setAlgorithmName(final String algorithmName) { + this.algorithmName = algorithmName; + } - @Pattern(regexp = RESOURCE_OR_EMPTY_REGEX) - private String testTypeJsonPath; + public void setAlgorithmOid(final String algorithmOid) { + this.algorithmOid = algorithmOid; + } - @NotNull - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String dgcDirectory; + public void setAndroidPackage(final String androidPackage) { + this.androidPackage = androidPackage; + } - @NotNull - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String boosterNotification; + public void setAppBundleId(final String appBundleId) { + this.appBundleId = appBundleId; + } - @NotNull - @Pattern(regexp = CHAR_AND_NUMBER_REGEX) - private String valuesetsFileName; + public void setFileName(final String fileName) { + this.fileName = fileName; + } - private String cclDirectory; + public void setSecurityProvider(final String securityProvider) { + this.securityProvider = securityProvider; + } - private String[] cclAllowList; + public void setVerificationKeyId(final String verificationKeyId) { + this.verificationKeyId = verificationKeyId; + } - private String allowList; + public void setVerificationKeyVersion(final String verificationKeyVersion) { + this.verificationKeyVersion = verificationKeyVersion; + } + } + + public static class StatisticsConfig { + + private String statisticPath; + + private String localStatisticPath; + + private String accessKey; + + private String secretKey; + + private String endpoint; + + private String bucket; + + private String pandemicRadarUrl; - private String allowListSignature; + private String pandemicRadarBmgUrl; - private String allowListCertificate; + public String getAccessKey() { + return accessKey; + } - private String[] supportedLanguages; + public String getBucket() { + return bucket; + } - private String exportArchiveName; + public String getEndpoint() { + return endpoint; + } - private Client client; + public String getLocalStatisticPath() { + return localStatisticPath; + } - private Client dscClient; + public String getPandemicRadarBmgUrl() { + return pandemicRadarBmgUrl; + } - public String getMahJsonPath() { - return mahJsonPath; + public String getPandemicRadarUrl() { + return pandemicRadarUrl; } - public void setMahJsonPath(String mahJsonPath) { - this.mahJsonPath = mahJsonPath; + public String getSecretKey() { + return secretKey; } - public String getProphylaxisJsonPath() { - return prophylaxisJsonPath; + public String getStatisticPath() { + return statisticPath; } - public void setProphylaxisJsonPath(String prophylaxisJsonPath) { - this.prophylaxisJsonPath = prophylaxisJsonPath; + public void setAccessKey(final String accessKey) { + this.accessKey = accessKey; } - public String getMedicinalProductsJsonPath() { - return medicinalProductsJsonPath; + public void setBucket(final String bucket) { + this.bucket = bucket; } - public void setMedicinalProductsJsonPath(String medicinalProductsJsonPath) { - this.medicinalProductsJsonPath = medicinalProductsJsonPath; + public void setEndpoint(final String endpoint) { + this.endpoint = endpoint; } - public String getDiseaseAgentTargetedJsonPath() { - return diseaseAgentTargetedJsonPath; + public void setLocalStatisticPath(final String localStatisticPath) { + this.localStatisticPath = localStatisticPath; } - public void setDiseaseAgentTargetedJsonPath(String diseaseAgentTargetedJsonPath) { - this.diseaseAgentTargetedJsonPath = diseaseAgentTargetedJsonPath; + public void setPandemicRadarBmgUrl(final String pandemicRadarBmgUrl) { + this.pandemicRadarBmgUrl = pandemicRadarBmgUrl; } - public String getTestManfJsonPath() { - return testManfJsonPath; + public void setPandemicRadarUrl(final String pandemicRadarUrl) { + this.pandemicRadarUrl = pandemicRadarUrl; } - public void setTestManfJsonPath(String testManfJsonPath) { - this.testManfJsonPath = testManfJsonPath; + public void setSecretKey(final String secretKey) { + this.secretKey = secretKey; } - public String getTestResultJsonPath() { - return testResultJsonPath; + public void setStatisticPath(final String statisticPath) { + this.statisticPath = statisticPath; } + } - public void setTestResultJsonPath(String testResultJsonPath) { - this.testResultJsonPath = testResultJsonPath; + public static class TekExport { + + @Pattern(regexp = FILE_NAME_WITH_TYPE_REGEX) + private String fileName; + @Pattern(regexp = CHAR_NUMBER_AND_SPACE_REGEX) + private String fileHeader; + @Min(0) + @Max(32) + private Integer fileHeaderWidth; + + public String getFileHeader() { + return fileHeader; } - public String getTestTypeJsonPath() { - return testTypeJsonPath; + public Integer getFileHeaderWidth() { + return fileHeaderWidth; } - public void setTestTypeJsonPath(String testTypeJsonPath) { - this.testTypeJsonPath = testTypeJsonPath; + public String getFileName() { + return fileName; } - public String getDgcDirectory() { - return dgcDirectory; + public void setFileHeader(final String fileHeader) { + this.fileHeader = fileHeader; } - public void setDgcDirectory(String dgcDirectory) { - this.dgcDirectory = dgcDirectory; + public void setFileHeaderWidth(final Integer fileHeaderWidth) { + this.fileHeaderWidth = fileHeaderWidth; } - public String getBoosterNotification() { - return boosterNotification; + public void setFileName(final String fileName) { + this.fileName = fileName; } + } - public void setBoosterNotification(String boosterNotification) { - this.boosterNotification = boosterNotification; + public static class TestData { + + private Integer seed; + private Integer exposuresPerHour; + private boolean distributionTestdataConsentToFederation; + + public boolean getDistributionTestdataConsentToFederation() { + return distributionTestdataConsentToFederation; } - public String getValuesetsFileName() { - return valuesetsFileName; + public Integer getExposuresPerHour() { + return exposuresPerHour; } - public void setValuesetsFileName(String valuesetsFileName) { - this.valuesetsFileName = valuesetsFileName; + public Integer getSeed() { + return seed; } - public String[] getSupportedLanguages() { - return supportedLanguages; + public void setDistributionTestdataConsentToFederation(final boolean distributionTestdataConsentToFederation) { + this.distributionTestdataConsentToFederation = distributionTestdataConsentToFederation; } - public void setSupportedLanguages(String[] supportedLanguages) { - this.supportedLanguages = supportedLanguages; + public void setExposuresPerHour(final Integer exposuresPerHour) { + this.exposuresPerHour = exposuresPerHour; } - public String getCclDirectory() { - return cclDirectory; + public void setSeed(final Integer seed) { + this.seed = seed; } + } + + private static final String PATH_REGEX = "^[/]?[a-zA-Z0-9_]{1,1024}(/[a-zA-Z0-9_]{1,1024}){0,256}[/]?$"; + private static final String RESOURCE_REGEX = "^(classpath:|file:[/]{1,3})?([a-zA-Z0-9_/\\.]{1,1010})$"; + private static final String RESOURCE_OR_EMPTY_REGEX = "(" + RESOURCE_REGEX + ")?"; + private static final String FILE_NAME_REGEX = "^[a-zA-Z0-9_-]{1,1024}$"; + private static final String FILE_NAME_WITH_TYPE_REGEX = "^[a-zA-Z0-9_-]{1,1024}\\.[a-z]{1,64}$"; + private static final String CHAR_AND_NUMBER_REGEX = "^[a-zA-Z0-9_-]{1,1024}$"; + private static final String CHAR_NUMBER_AND_SPACE_REGEX = "^[a-zA-Z0-9_\\s]{1,32}$"; + private static final String NO_WHITESPACE_REGEX = "^[\\S]+$"; + private static final String URL_REGEX = "^http[s]?://[a-zA-Z0-9-_]{1,1024}([\\./][a-zA-Z0-9-_]{1,1024}){0,256}[/]?$"; + private static final String NUMBER_REGEX = "^[0-9]{1,256}$"; + private static final String VERSION_REGEX = "^v[0-9]{1,256}$"; + private static final String ALGORITHM_OID_REGEX = "^[0-9]{1,256}[\\.[0-9]{1,256}]{0,256}$"; + private static final String BUNDLE_REGEX = "^[a-z-]{1,256}[\\.[a-z-]{1,256}]{0,256}$"; + private static final String PRIVATE_KEY_REGEX = + "^(classpath:|file:\\.?[/]{1,8})[a-zA-Z0-9_-]{1,256}:?[/[a-zA-Z0-9_-]{1,256}]{0,256}(.pem)?$"; + private Paths paths; + private TestData testData; + @Min(0) + @Max(28) + private Integer retentionDays; + @Min(0) + @Max(4000) + private int srsTypeStatisticsDays; + @Min(120) + @Max(720) + private Integer expiryPolicyMinutes; + @Min(0) + @Max(200) + private Integer shiftingPolicyThreshold; + @Min(600000) + @Max(750000) + private Integer maximumNumberOfKeysPerBundle; + @Pattern(regexp = FILE_NAME_REGEX) + private String outputFileName; + @Pattern(regexp = FILE_NAME_REGEX) + private String outputFileNameV2; + private Boolean includeIncompleteDays; + private Boolean includeIncompleteHours; + private String euPackageName; + private Boolean applyPoliciesForAllCountries; + private String cardIdSequence; + private TekExport tekExport; + private Signature signature; + private Api api; + + private ObjectStore objectStore; + + private List appFeatures; + + @NotEmpty + private String[] supportedCountries; + + private AppVersions appVersions; + + private AppConfigParameters appConfigParameters; + + private StatisticsConfig statistics; + + private QrCodePosterTemplate iosQrCodePosterTemplate; + + private QrCodePosterTemplate androidQrCodePosterTemplate; + + private PresenceTracingParameters presenceTracingParameters; + + private DigitalGreenCertificate digitalGreenCertificate; + + private Integer connectionPoolSize; + + private String defaultArchiveName; + + private Integer minimumTrlValueAllowed; + + private Integer daysToPublish; + + private DccRevocation dccRevocation; + + @Min(0) + @Max(100) + private int infectionThreshold; + + public QrCodePosterTemplate getAndroidQrCodePosterTemplate() { + return androidQrCodePosterTemplate; + } + + public Api getApi() { + return api; + } + + public AppConfigParameters getAppConfigParameters() { + return appConfigParameters; + } + + public List getAppFeatures() { + return 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 Boolean getApplyPoliciesForAllCountries() { + return applyPoliciesForAllCountries; + } + + public AppVersions getAppVersions() { + return appVersions; + } + + public String getCardIdSequence() { + return cardIdSequence; + } + + public Integer getConnectionPoolSize() { + return connectionPoolSize; + } + + public Integer getDaysToPublish() { + return daysToPublish == null ? retentionDays : daysToPublish; + } + + public DccRevocation getDccRevocation() { + return dccRevocation; + } + + public String getDefaultArchiveName() { + return defaultArchiveName; + } + + public DigitalGreenCertificate getDigitalGreenCertificate() { + return digitalGreenCertificate; + } + + public String getEuPackageName() { + return euPackageName; + } + + public Integer getExpiryPolicyMinutes() { + return expiryPolicyMinutes; + } + + public Boolean getIncludeIncompleteDays() { + return includeIncompleteDays; + } + + public Boolean getIncludeIncompleteHours() { + return includeIncompleteHours; + } - public void setCclDirectory(String cclDirectory) { - this.cclDirectory = cclDirectory; - } + public int getInfectionThreshold() { + return infectionThreshold; + } - public String[] getCclAllowList() { - return cclAllowList; - } + public QrCodePosterTemplate getIosQrCodePosterTemplate() { + return iosQrCodePosterTemplate; + } - public void setCclAllowList(String[] cclAllowList) { - this.cclAllowList = cclAllowList; - } + public Integer getMaximumNumberOfKeysPerBundle() { + return maximumNumberOfKeysPerBundle; + } - /** - * getAllowList. - * - * @return AllowList - */ - public AllowList getAllowList() { - return SerializationUtils.deserializeJson(allowList, - typeFactory -> typeFactory - .constructType(AllowList.class)); - } + public Integer getMinimumTrlValueAllowed() { + return minimumTrlValueAllowed; + } - public String getAllowListAsString() { - return allowList; - } + public ObjectStore getObjectStore() { + return objectStore; + } - public void setAllowList(String allowList) { - this.allowList = allowList; - } + public String getOutputFileName() { + return outputFileName; + } - public byte[] getAllowListSignature() { - return Hex.decode(allowListSignature); - } + public String getOutputFileNameV2() { + return outputFileNameV2; + } - public void setAllowListSignature(String allowListSignature) { - this.allowListSignature = allowListSignature; - } + public Paths getPaths() { + return paths; + } - public String getAllowListCertificate() { - return allowListCertificate; - } + public PresenceTracingParameters getPresenceTracingParameters() { + return presenceTracingParameters; + } - public void setAllowListCertificate(String allowListCertificate) { - this.allowListCertificate = allowListCertificate; - } + public Integer getRetentionDays() { + return retentionDays; + } - public Client getClient() { - return client; - } + public Integer getShiftingPolicyThreshold() { + return shiftingPolicyThreshold; + } - public void setClient(Client client) { - this.client = client; - } + public Signature getSignature() { + return signature; + } - public Client getDscClient() { - return dscClient; - } + public int getSrsTypeStatisticsDays() { + return srsTypeStatisticsDays; + } - public void setDscClient(Client dscClient) { - this.dscClient = dscClient; - } + public StatisticsConfig getStatistics() { + return statistics; + } - public String getExportArchiveName() { - return exportArchiveName; - } + public String[] getSupportedCountries() { + return supportedCountries; + } - public void setExportArchiveName(String exportArchiveName) { - this.exportArchiveName = exportArchiveName; - } + public TekExport getTekExport() { + return tekExport; } - public static class AllowList { + public TestData getTestData() { + return testData; + } - private List certificates; + public void setAndroidQrCodePosterTemplate(final QrCodePosterTemplate androidQrCodePosterTemplate) { + this.androidQrCodePosterTemplate = androidQrCodePosterTemplate; + } - private List serviceProviders; + public void setApi(final Api api) { + this.api = api; + } - public List getCertificates() { - return certificates; - } + public void setAppConfigParameters(final AppConfigParameters appConfigParameters) { + this.appConfigParameters = appConfigParameters; + } - @JsonProperty("certificates") - public void setCertificates(List certificates) { - this.certificates = certificates; - } + public void setAppFeatures(final List appFeatures) { + this.appFeatures = appFeatures; + } - public List getServiceProviders() { - return serviceProviders; - } + public void setApplyPoliciesForAllCountries(final Boolean applyPoliciesForAllCountries) { + this.applyPoliciesForAllCountries = applyPoliciesForAllCountries; + } - @JsonProperty("serviceProviders") - public void setServiceProviders(List serviceProviders) { - this.serviceProviders = serviceProviders; - } + public void setAppVersions(final AppVersions appVersions) { + this.appVersions = appVersions; + } - @JsonInclude(Include.NON_NULL) - @JsonIgnoreProperties(ignoreUnknown = true) - public static class ServiceProvider { + public void setCardIdSequence(final String cardIdSequence) { + this.cardIdSequence = cardIdSequence; + } - @JsonProperty("serviceProviderAllowlistEndpoint") - private String serviceProviderAllowlistEndpoint; - @JsonProperty("fingerprint256") - private String fingerprint256; + public void setConnectionPoolSize(final Integer connectionPoolSize) { + this.connectionPoolSize = connectionPoolSize; + } - public String getServiceProviderAllowlistEndpoint() { - return serviceProviderAllowlistEndpoint; - } + public void setDaysToPublish(final Integer daysToPublish) { + this.daysToPublish = daysToPublish; + } - public void setServiceProviderAllowlistEndpoint(String serviceProviderAllowlistEndpoint) { - this.serviceProviderAllowlistEndpoint = serviceProviderAllowlistEndpoint; - } + public void setDccRevocation(final DccRevocation dccRevocation) { + this.dccRevocation = dccRevocation; + } - public String getFingerprint256() { - return fingerprint256; - } + public void setDefaultArchiveName(final String defaultArchiveName) { + this.defaultArchiveName = defaultArchiveName; + } - public void setFingerprint256(String fingerprint256) { - this.fingerprint256 = fingerprint256; - } - } + public void setDigitalGreenCertificate(final DigitalGreenCertificate digitalGreenCertificate) { + this.digitalGreenCertificate = digitalGreenCertificate; + } - @JsonInclude(Include.NON_NULL) - @JsonIgnoreProperties(ignoreUnknown = true) - public static class CertificateAllowList { + public void setEuPackageName(final String euPackageName) { + this.euPackageName = euPackageName; + } - private String serviceProvider; - private String hostname; - private String fingerprint256; + public void setExpiryPolicyMinutes(final Integer expiryPolicyMinutes) { + this.expiryPolicyMinutes = expiryPolicyMinutes; + } - @JsonProperty("serviceProviderAllowlistEndpoint") - private String serviceProviderAllowlistEndpoint; + public void setIncludeIncompleteDays(final Boolean includeIncompleteDays) { + this.includeIncompleteDays = includeIncompleteDays; + } - public String getServiceProvider() { - return serviceProvider; - } + public void setIncludeIncompleteHours(final Boolean includeIncompleteHours) { + this.includeIncompleteHours = includeIncompleteHours; + } - @JsonProperty("serviceProvider") - public void setServiceProvider(String serviceProvider) { - this.serviceProvider = serviceProvider; - } + public void setInfectionThreshold(final int infectionThreshold) { + this.infectionThreshold = infectionThreshold; + } - public String getHostname() { - return hostname; - } + public void setIosQrCodePosterTemplate(final QrCodePosterTemplate iosQrCodePosterTemplate) { + this.iosQrCodePosterTemplate = iosQrCodePosterTemplate; + } - @JsonProperty("hostname") - public void setHostname(String hostname) { - this.hostname = hostname; - } + public void setMaximumNumberOfKeysPerBundle(final Integer maximumNumberOfKeysPerBundle) { + this.maximumNumberOfKeysPerBundle = maximumNumberOfKeysPerBundle; + } - public String getFingerprint256() { - return fingerprint256; - } + public void setMinimumTrlValueAllowed(final Integer minimumTrlValueAllowed) { + this.minimumTrlValueAllowed = minimumTrlValueAllowed; + } - @JsonProperty("fingerprint256") - public void setFingerprint256(String fingerprint256) { - this.fingerprint256 = fingerprint256; - } + public void setObjectStore( + final ObjectStore objectStore) { + this.objectStore = objectStore; + } - public String getServiceProviderAllowlistEndpoint() { - return serviceProviderAllowlistEndpoint; - } + public void setOutputFileName(final String outputFileName) { + this.outputFileName = outputFileName; + } - public void setServiceProviderAllowlistEndpoint(String serviceProviderAllowlistEndpoint) { - this.serviceProviderAllowlistEndpoint = serviceProviderAllowlistEndpoint; - } - } + public void setOutputFileNameV2(final String outputFileNameV2) { + this.outputFileNameV2 = outputFileNameV2; } - public static class DccRevocation { + public void setPaths(final Paths paths) { + this.paths = paths; + } - private Client client; - private String certificate; - private String dccListPath; - private String dccRevocationDirectory; + public void setPresenceTracingParameters( + final PresenceTracingParameters presenceTracingParameters) { + this.presenceTracingParameters = presenceTracingParameters; + } - public Client getClient() { - return client; - } + public void setRetentionDays(final Integer retentionDays) { + this.retentionDays = retentionDays; + } - public void setClient(Client client) { - this.client = client; - } + public void setShiftingPolicyThreshold(final Integer shiftingPolicyThreshold) { + this.shiftingPolicyThreshold = shiftingPolicyThreshold; + } - public String getCertificate() { - return certificate; - } + public void setSignature(final Signature signature) { + this.signature = signature; + } - public void setCertificate(String certificate) { - this.certificate = certificate; - } + public void setSrsTypeStatisticsDays(final int srsTypeStatisticsDays) { + this.srsTypeStatisticsDays = srsTypeStatisticsDays; + } - public String getDccListPath() { - return dccListPath; - } + public void setStatistics(final StatisticsConfig statistics) { + this.statistics = statistics; + } - public void setDccListPath(String dccListPath) { - this.dccListPath = dccListPath; - } + public void setSupportedCountries(final String[] supportedCountries) { + this.supportedCountries = supportedCountries; + } - public String getDccRevocationDirectory() { - return dccRevocationDirectory; - } + public void setTekExport(final TekExport tekExport) { + this.tekExport = tekExport; + } - public void setDccRevocationDirectory(String dccRevocationDirectory) { - this.dccRevocationDirectory = dccRevocationDirectory; - } + public void setTestData(final TestData testData) { + this.testData = testData; } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/StatisticsToProtobufMapping.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/StatisticsToProtobufMapping.java index 1f60434150..5100f2d4b7 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/StatisticsToProtobufMapping.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/StatisticsToProtobufMapping.java @@ -128,9 +128,12 @@ public Statistics constructProtobufStatistics() { } private Iterable buildAllLinkCards() { - return Collections.singleton( + return List.of( LinkCard.newBuilder().setHeader(CardHeader.newBuilder().setCardId(Cards.PANDEMIC_RADAR_CARD.ordinal()).build()) - .setUrl(distributionServiceConfig.getStatistics().getPandemicRadarUrl()).build()); + .setUrl(distributionServiceConfig.getStatistics().getPandemicRadarUrl()).build(), + LinkCard.newBuilder() + .setHeader(CardHeader.newBuilder().setCardId(Cards.PANDEMIC_RADAR_BMG_CARD.ordinal()).build()) + .setUrl(distributionServiceConfig.getStatistics().getPandemicRadarBmgUrl()).build()); } private List getAllCardIdSequence() { diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/Cards.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/Cards.java index fc57dca2aa..f2c047fe84 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/Cards.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/Cards.java @@ -21,67 +21,98 @@ public enum Cards { /** * Empty card. */ - EMPTY_CARD(new EmptyCardFactory(), "EMPTY CARD"), + EMPTY_CARD("EMPTY CARD"), /** * Infections Card. */ - INFECTIONS_CARD(new InfectionsCardFactory(), "Infections Card"), + INFECTIONS_CARD("Infections Card"), /** * Incidence Card. */ - INCIDENCE_CARD(new IncidenceCardFactory(), "Incidence Card"), + INCIDENCE_CARD("Incidence Card"), /** * Key Submission Card. */ - KEY_SUBMISSION_CARD(new KeySubmissionCardFactory(), "Key Submission Card"), + KEY_SUBMISSION_CARD("Key Submission Card"), /** * Reproduction Number Card. */ - REPRODUCTION_NUMBER_CARD(new ReproductionNumberCardFactory(), "Reproduction Number Card"), + REPRODUCTION_NUMBER_CARD("Reproduction Number Card"), /** * First Vaccination Card. */ - FIRST_VACCINATION_CARD(new FirstVaccinationCardFactory(), "First Vaccination Card"), + FIRST_VACCINATION_CARD("First Vaccination Card"), /** * Fully Vaccincated Card. */ - FULLY_VACCINATED_CARD(new FullyVaccinatedCardFactory(), "Fully Vaccincated Card"), + FULLY_VACCINATED_CARD("Fully Vaccincated Card"), /** * Vaccination Doses Card. */ - VACCINATION_DOSES_CARD(new VaccinationDosesCardFactory(), "Vaccination Doses Card"), + VACCINATION_DOSES_CARD("Vaccination Doses Card"), /** * Hospitalization Incidence Card. */ - HOSPITALIZATION_INCIDENCE_CARD(new HospitalizationIncidenceCardFactory(), "Hospitalization Incidence Card"), + HOSPITALIZATION_INCIDENCE_CARD("Hospitalization Incidence Card"), /** * Intensive Care Card. */ - INTENSIVE_CARE_CARD(new IntensiveCareCardFactory(), "Intensive Care Card"), + INTENSIVE_CARE_CARD("Intensive Care Card"), /** * Joined Incidence Card. */ - JOINED_INCIDENCE_CARD(new JoinedIncidenceCardFactory(), "Joined incidence Card"), + JOINED_INCIDENCE_CARD("Joined incidence Card"), /** * Third Dose Card. */ - BOOSTER_VACCINATED_CARD(new BoosterVaccinatedCardFactory(), "Booster Vaccinated Card"), - - PANDEMIC_RADAR_CARD(new LinkCardFactory(), "Link Card"); + BOOSTER_VACCINATED_CARD("Booster Vaccinated Card"), + /** + * Outdated pandemic radar URL card. + */ + PANDEMIC_RADAR_CARD("Link Card"), + /** + * New pandemic radar (BMG) URL card. + */ + PANDEMIC_RADAR_BMG_CARD("Pandemic Radar Card (BMG)"), + ; /** * Get card factory by ID. * - * @param id {@link #ordinal()} + * @param card {@link #ordinal()} * @param config The distribution configuration used to get the infection threshold parameter. * @return {@link #getFactory()} */ - public static HeaderCardFactory getFactoryFor(final int id, final DistributionServiceConfig config) { - try { - values()[id].getFactory().setConfig(config); - return values()[id].getFactory(); - } catch (final ArrayIndexOutOfBoundsException e) { - return EMPTY_CARD.getFactory(); + public static HeaderCardFactory getFactoryFor(final Cards card, final DistributionServiceConfig config) { + switch (card) { + case INFECTIONS_CARD: + return new InfectionsCardFactory(config); + case INCIDENCE_CARD: + return new IncidenceCardFactory(config); + case KEY_SUBMISSION_CARD: + return new KeySubmissionCardFactory(config); + case REPRODUCTION_NUMBER_CARD: + return new ReproductionNumberCardFactory(config); + case FIRST_VACCINATION_CARD: + return new FirstVaccinationCardFactory(config); + case FULLY_VACCINATED_CARD: + return new FullyVaccinatedCardFactory(config); + case VACCINATION_DOSES_CARD: + return new VaccinationDosesCardFactory(config); + case HOSPITALIZATION_INCIDENCE_CARD: + return new HospitalizationIncidenceCardFactory(config); + case INTENSIVE_CARE_CARD: + return new IntensiveCareCardFactory(config); + case JOINED_INCIDENCE_CARD: + return new JoinedIncidenceCardFactory(config); + case BOOSTER_VACCINATED_CARD: + return new BoosterVaccinatedCardFactory(config); + case PANDEMIC_RADAR_CARD: + return new LinkCardFactory(config, PANDEMIC_RADAR_CARD); + case PANDEMIC_RADAR_BMG_CARD: + return new LinkCardFactory(config, PANDEMIC_RADAR_BMG_CARD); + default: + return new EmptyCardFactory(config); } } @@ -99,19 +130,12 @@ public static String getNameFor(final int id) { } } - final HeaderCardFactory factory; - final String name; - Cards(final HeaderCardFactory factory, final String name) { - this.factory = factory; + Cards(final String name) { this.name = name; } - HeaderCardFactory getFactory() { - return factory; - } - String getName() { return name; } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/KeyFigureCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/KeyFigureCardFactory.java index 6cf70ed5fd..184b2163b1 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/KeyFigureCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/KeyFigureCardFactory.java @@ -1,20 +1,38 @@ package app.coronawarn.server.services.distribution.statistics.keyfigurecard; import static app.coronawarn.server.services.distribution.statistics.keyfigurecard.Cards.getFactoryFor; +import static app.coronawarn.server.services.distribution.statistics.keyfigurecard.Cards.values; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; +import app.coronawarn.server.services.distribution.statistics.keyfigurecard.factory.EmptyCardFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class KeyFigureCardFactory { + private static final Logger logger = LoggerFactory.getLogger(KeyFigureCardFactory.class); + @Autowired DistributionServiceConfig distributionServiceConfig; - public KeyFigureCard createKeyFigureCard(StatisticsJsonStringObject stats, int cardId) { - return getFactoryFor(cardId, this.distributionServiceConfig).makeKeyFigureCard(stats); + /** + * Creates a new statistics card, based upon the given ordinal. + * + * @param stats data for the card. + * @param cardId ordinal number of {@link Cards}. + * @return a new {@link KeyFigureCard}. + */ + public KeyFigureCard createKeyFigureCard(final StatisticsJsonStringObject stats, final int cardId) { + try { + return getFactoryFor(values()[cardId], distributionServiceConfig).makeKeyFigureCard(stats); + } catch (final IndexOutOfBoundsException e) { + logger.error(Cards.class + " doesn't contain value for: " + cardId, e); + return new EmptyCardFactory(distributionServiceConfig).makeKeyFigureCard(stats); + } } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/BoosterVaccinatedCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/BoosterVaccinatedCardFactory.java index 08f2e21eb8..f87203236f 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/BoosterVaccinatedCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/BoosterVaccinatedCardFactory.java @@ -8,12 +8,17 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.TrendSemantic; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import java.util.List; import java.util.Optional; public class BoosterVaccinatedCardFactory extends HeaderCardFactory { + public BoosterVaccinatedCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return BOOSTER_VACCINATED_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/EmptyCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/EmptyCardFactory.java index 35c5bc77ae..531e0ba6da 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/EmptyCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/EmptyCardFactory.java @@ -2,8 +2,14 @@ import static app.coronawarn.server.services.distribution.statistics.keyfigurecard.Cards.EMPTY_CARD; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; + public class EmptyCardFactory extends HeaderCardFactory { + public EmptyCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return EMPTY_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FirstVaccinationCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FirstVaccinationCardFactory.java index a7955345f0..80154731e1 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FirstVaccinationCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FirstVaccinationCardFactory.java @@ -8,12 +8,17 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.TrendSemantic; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import java.util.List; import java.util.Optional; public class FirstVaccinationCardFactory extends HeaderCardFactory { + public FirstVaccinationCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return FIRST_VACCINATION_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FullyVaccinatedCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FullyVaccinatedCardFactory.java index b4e3abbbb2..59b37871c8 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FullyVaccinatedCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/FullyVaccinatedCardFactory.java @@ -8,12 +8,17 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.TrendSemantic; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import java.util.List; import java.util.Optional; public class FullyVaccinatedCardFactory extends HeaderCardFactory { + public FullyVaccinatedCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return FULLY_VACCINATED_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java index 1635d04755..e499e3b558 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java @@ -20,6 +20,10 @@ public abstract class HeaderCardFactory { protected DistributionServiceConfig config; + public HeaderCardFactory(final DistributionServiceConfig config) { + this.config = config; + } + /** * Create KeyFigureCard object. Calls the children method `buildKeyFigureCard` for card specific properties. This * method adds the generic CardHeader that all KeyFigureCards must have. diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HospitalizationIncidenceCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HospitalizationIncidenceCardFactory.java index 5cd70bdce1..7de8ca91da 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HospitalizationIncidenceCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HospitalizationIncidenceCardFactory.java @@ -6,6 +6,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.Rank; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; @@ -13,6 +14,10 @@ public class HospitalizationIncidenceCardFactory extends HeaderCardFactory { + public HospitalizationIncidenceCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return HOSPITALIZATION_INCIDENCE_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IncidenceCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IncidenceCardFactory.java index 50ff578fa0..38f9514eee 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IncidenceCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IncidenceCardFactory.java @@ -6,6 +6,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.Rank; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; @@ -13,6 +14,10 @@ public class IncidenceCardFactory extends HeaderCardFactory { + public IncidenceCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return INCIDENCE_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/InfectionsCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/InfectionsCardFactory.java index 17efc322a6..80a42b916a 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/InfectionsCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/InfectionsCardFactory.java @@ -10,6 +10,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; @@ -19,6 +20,10 @@ public class InfectionsCardFactory extends HeaderCardFactory { + public InfectionsCardFactory(final DistributionServiceConfig config) { + super(config); + } + private static final Logger logger = LoggerFactory.getLogger(InfectionsCardFactory.class); @Override diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IntensiveCareCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IntensiveCareCardFactory.java index 094de5ad3a..0ed3c8b256 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IntensiveCareCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/IntensiveCareCardFactory.java @@ -6,6 +6,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.Rank; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; @@ -13,6 +14,10 @@ public class IntensiveCareCardFactory extends HeaderCardFactory { + public IntensiveCareCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return INTENSIVE_CARE_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/JoinedIncidenceCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/JoinedIncidenceCardFactory.java index 5356cc8d22..8afdaeda20 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/JoinedIncidenceCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/JoinedIncidenceCardFactory.java @@ -7,6 +7,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.Rank; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.time.LocalDate; @@ -15,6 +16,10 @@ public class JoinedIncidenceCardFactory extends HeaderCardFactory { + public JoinedIncidenceCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return JOINED_INCIDENCE_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/KeySubmissionCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/KeySubmissionCardFactory.java index e534bd00a0..f7781e4fa9 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/KeySubmissionCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/KeySubmissionCardFactory.java @@ -8,14 +8,18 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.TrendSemantic; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; import java.util.Optional; - public class KeySubmissionCardFactory extends HeaderCardFactory { + public KeySubmissionCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return KEY_SUBMISSION_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/LinkCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/LinkCardFactory.java index d44b25e9a7..d691fe0866 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/LinkCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/LinkCardFactory.java @@ -1,11 +1,19 @@ package app.coronawarn.server.services.distribution.statistics.keyfigurecard.factory; -import static app.coronawarn.server.services.distribution.statistics.keyfigurecard.Cards.PANDEMIC_RADAR_CARD; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; +import app.coronawarn.server.services.distribution.statistics.keyfigurecard.Cards; public class LinkCardFactory extends HeaderCardFactory { + public LinkCardFactory(final DistributionServiceConfig config, final Cards card) { + super(config); + this.card = card; + } + + private final Cards card; + @Override public int getCardId() { - return PANDEMIC_RADAR_CARD.ordinal(); + return card.ordinal(); } } diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/ReproductionNumberCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/ReproductionNumberCardFactory.java index 6989ad4bea..cbbce800a5 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/ReproductionNumberCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/ReproductionNumberCardFactory.java @@ -6,6 +6,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.Rank; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; @@ -13,6 +14,10 @@ public class ReproductionNumberCardFactory extends HeaderCardFactory { + public ReproductionNumberCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return REPRODUCTION_NUMBER_CARD.ordinal(); diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/VaccinationDosesCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/VaccinationDosesCardFactory.java index bf39072404..1778996a1c 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/VaccinationDosesCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/VaccinationDosesCardFactory.java @@ -8,6 +8,7 @@ import app.coronawarn.server.common.protocols.internal.stats.KeyFigure.TrendSemantic; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard; import app.coronawarn.server.common.protocols.internal.stats.KeyFigureCard.Builder; +import app.coronawarn.server.services.distribution.config.DistributionServiceConfig; import app.coronawarn.server.services.distribution.statistics.StatisticsJsonStringObject; import app.coronawarn.server.services.distribution.statistics.keyfigurecard.ValueTrendCalculator; import java.util.List; @@ -15,6 +16,10 @@ public class VaccinationDosesCardFactory extends HeaderCardFactory { + public VaccinationDosesCardFactory(final DistributionServiceConfig config) { + super(config); + } + @Override protected int getCardId() { return VACCINATION_DOSES_CARD.ordinal(); diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index 9031bb7037..f3053eaed2 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -40,7 +40,7 @@ services: eu-package-name: EUR # Indicates whether the shifting and expiry policies are applied to all supported countries during distribution. apply-policies-for-all-countries: false - card-id-sequence: ${STATS_CARD_ID_SEQUENCE:[12,999,10,2,8,9,1,3,4,5,6,11,7]} + card-id-sequence: ${STATS_CARD_ID_SEQUENCE:[13,12,999,10,2,8,9,1,3,4,5,6,11,7]} # Local paths, that are used during the export creation. connection-pool-size: 200 default-archive-name: export.bin @@ -120,6 +120,7 @@ services: endpoint: ${STATISTICS_FILE_S3_ENDPOINT:} bucket: ${STATISTICS_FILE_S3_BUCKET:obs-cwa-public-dev} pandemic-radar-url: ${STATISTICS_PANDEMIC_RADAR_URL:https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home} + pandemic-radar-bmg-url: ${STATISTICS_PANDEMIC_RADAR_BMG_URL:https://corona-pandemieradar.de/} app-features: - label: unencrypted-checkins-enabled diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 32de58b9a7..9c7ebb2779 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -76,7 +76,8 @@ services: secret-key: secretKey endpoint: https://localhost bucket: ${STATS_S3_BUCKET:obs-cwa-public-dev} - pandemic-radar-url: ${STATISTICS_PANDEMIC_RADAR_URL:https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home} + pandemic-radar-url: https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home + pandemic-radar-bmg-url: https://corona-pandemieradar.de/ app-features: - label: unencrypted-checkins-enabled From eeb8faef1a89298384d447074102d2899f84271a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 7 Dec 2022 12:40:36 +0100 Subject: [PATCH 44/83] + :lang --- services/distribution/src/main/resources/application.yaml | 2 +- services/distribution/src/test/resources/application.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/distribution/src/main/resources/application.yaml b/services/distribution/src/main/resources/application.yaml index f3053eaed2..ade9d30be2 100644 --- a/services/distribution/src/main/resources/application.yaml +++ b/services/distribution/src/main/resources/application.yaml @@ -120,7 +120,7 @@ services: endpoint: ${STATISTICS_FILE_S3_ENDPOINT:} bucket: ${STATISTICS_FILE_S3_BUCKET:obs-cwa-public-dev} pandemic-radar-url: ${STATISTICS_PANDEMIC_RADAR_URL:https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home} - pandemic-radar-bmg-url: ${STATISTICS_PANDEMIC_RADAR_BMG_URL:https://corona-pandemieradar.de/} + pandemic-radar-bmg-url: ${STATISTICS_PANDEMIC_RADAR_BMG_URL:https://corona-pandemieradar.de/:lang} app-features: - label: unencrypted-checkins-enabled diff --git a/services/distribution/src/test/resources/application.yaml b/services/distribution/src/test/resources/application.yaml index 9c7ebb2779..a20d7a12d5 100644 --- a/services/distribution/src/test/resources/application.yaml +++ b/services/distribution/src/test/resources/application.yaml @@ -77,7 +77,7 @@ services: endpoint: https://localhost bucket: ${STATS_S3_BUCKET:obs-cwa-public-dev} pandemic-radar-url: https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home - pandemic-radar-bmg-url: https://corona-pandemieradar.de/ + pandemic-radar-bmg-url: https://corona-pandemieradar.de/:lang app-features: - label: unencrypted-checkins-enabled From 7a1ba6a39d1a4f14ffb3e775343f770b02756d8f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 7 Dec 2022 13:05:25 +0100 Subject: [PATCH 45/83] fix code smells --- .../keyfigurecard/factory/HeaderCardFactory.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java index e499e3b558..05aea10810 100644 --- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java +++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/statistics/keyfigurecard/factory/HeaderCardFactory.java @@ -13,14 +13,13 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import org.springframework.util.ObjectUtils; public abstract class HeaderCardFactory { protected DistributionServiceConfig config; - public HeaderCardFactory(final DistributionServiceConfig config) { + protected HeaderCardFactory(final DistributionServiceConfig config) { this.config = config; } @@ -53,9 +52,7 @@ private KeyFigureCard.Builder makeBuilderWithDefaultHeader(LocalDate dateTime) { } private void throwIfNullFieldsFound(StatisticsJsonStringObject stats) { - var nullFieldsOrZeroOrLessThanZero = getRequiredFieldValues(stats).stream() - .filter(Optional::isEmpty) - .collect(Collectors.toList()); + var nullFieldsOrZeroOrLessThanZero = getRequiredFieldValues(stats).stream().filter(Optional::isEmpty).toList(); if (!nullFieldsOrZeroOrLessThanZero.isEmpty()) { throw new MissingPropertyException(this.getCardId()); } @@ -78,8 +75,4 @@ protected KeyFigureCard buildKeyFigureCard(StatisticsJsonStringObject stats, Bui protected List> getRequiredFieldValues(StatisticsJsonStringObject stats) { return Collections.emptyList(); } - - public void setConfig(final DistributionServiceConfig config) { - this.config = config; - } } From 8f8b331b9c2ab64ee3e4c15713a931eec78b5f60 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Wed, 7 Dec 2022 15:19:13 +0100 Subject: [PATCH 46/83] forkedProcessExitTimeoutInSeconds 60 https://maven.apache.org/surefire/maven-surefire-plugin/examples/shutdown.html Surefire is going to kill self fork JVM. The exit has elapsed 30 seconds after System.exit(0) --- pom.xml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 4184834868..ddfc9380a4 100644 --- a/pom.xml +++ b/pom.xml @@ -531,6 +531,7 @@ + 120 **/*IT.java **/*IntegrationTest.java @@ -540,14 +541,14 @@ ${basedir}/target/classes none - - off - OFF - OFF - OFF - OFF - off - + + off + OFF + OFF + OFF + OFF + off + integration-test @@ -585,6 +586,7 @@ maven-surefire-plugin 3.0.0-M7 + 60 s3-integration false From 2e0ce41ef35fa81ce0dfe8056d0bf2cbd73901e4 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 8 Dec 2022 16:52:35 +0100 Subject: [PATCH 47/83] DiagnosisKeyRepository + exists(Collection) --- .../repository/DiagnosisKeyRepository.java | 12 +++ .../DiagnosisKeyRepositoryTest.java | 78 +++++++++++++++---- 2 files changed, 73 insertions(+), 17 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 18628e3624..68a4c34849 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 @@ -2,6 +2,7 @@ import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import java.time.LocalDate; +import java.util.Collection; import java.util.List; import org.springframework.data.jdbc.repository.query.Modifying; import org.springframework.data.jdbc.repository.query.Query; @@ -25,6 +26,17 @@ public interface DiagnosisKeyRepository extends PagingAndSortingRepository 0 THEN 1 ELSE 0 END AS BIT) " + + "FROM diagnosis_key " + + "WHERE key_data in (:key_data)") + boolean exists(@Param("key_data") Collection keyData); + /** * Counts all entries that have a submission timestamp older than the specified one. * diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java index 0c223051e8..8e97e9c8c8 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java @@ -8,6 +8,7 @@ import app.coronawarn.server.common.protocols.external.exposurenotification.ReportType; import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; import java.time.LocalDate; +import java.util.List; import java.util.Random; import java.util.Set; import org.junit.jupiter.api.AfterEach; @@ -21,24 +22,74 @@ @DataJdbcTest class DiagnosisKeyRepositoryTest { + public static DiagnosisKey randomKey() { + final SubmissionType type = SubmissionType.SUBMISSION_TYPE_PCR_TEST; + final byte[] id = new byte[16]; + new Random().nextBytes(id); + + return DiagnosisKey.builder() + .withKeyDataAndSubmissionType(id, type) + .withRollingStartIntervalNumber(600) + .withTransmissionRiskLevel(2) + .withRollingPeriod(1) + .withSubmissionTimestamp(0L) + .withCountryCode("DE") + .build(); + } + @Autowired private DiagnosisKeyRepository repository; - @AfterEach - void tearDown() { - repository.deleteAll(); + @Test + void checkKeyData() { + final DiagnosisKey key1 = randomKey(); + final DiagnosisKey key2 = randomKey(); + final DiagnosisKey key3 = randomKey(); + final DiagnosisKey key4 = randomKey(); + assertFalse(repository.exists(key1.getKeyData(), key1.getSubmissionType().name())); + assertFalse(repository.exists(key2.getKeyData(), key2.getSubmissionType().name())); + assertFalse(repository.exists(key3.getKeyData(), key3.getSubmissionType().name())); + assertFalse(repository.exists(key4.getKeyData(), key4.getSubmissionType().name())); + save(key1); + assertTrue(repository.exists(List.of(key1.getKeyData()))); + assertFalse(repository.exists(List.of(key2.getKeyData(), key3.getKeyData(), key4.getKeyData()))); + save(key2); + assertTrue(repository.exists(List.of(key2.getKeyData()))); + assertFalse(repository.exists(List.of(key3.getKeyData(), key4.getKeyData()))); + save(key3); + save(key4); + assertTrue(repository.exists(List.of(key1.getKeyData(), key2.getKeyData(), key3.getKeyData(), key4.getKeyData()))); + } + + @ParameterizedTest + @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) + void recordSrsTest(final SubmissionType type) { + assertTrue(repository.recordSrs(type.name())); + assertEquals(1, repository.countTodaysSrs()); + final LocalDate tomorrow = LocalDate.now().plusDays(1); + assertEquals(1, repository.countSrsOlderThan(tomorrow)); + repository.deleteSrsOlderThan(tomorrow); + assertEquals(0, repository.countTodaysSrs()); + assertEquals(0, repository.countSrsOlderThan(tomorrow)); + } + + void save(final DiagnosisKey key) { + repository.saveDoNothingOnConflict( + key.getKeyData(), key.getRollingStartIntervalNumber(), key.getRollingPeriod(), + key.getSubmissionTimestamp(), key.getTransmissionRiskLevel(), + null, null, null, 0, + key.isConsentToFederation(), key.getSubmissionType().name()); } @Test void shouldCheckExistence() { - - SubmissionType type = SubmissionType.SUBMISSION_TYPE_PCR_TEST; - byte[] id = new byte[16]; + final SubmissionType type = SubmissionType.SUBMISSION_TYPE_PCR_TEST; + final byte[] id = new byte[16]; new Random().nextBytes(id); assertFalse(repository.exists(id, type.name())); - DiagnosisKey key = DiagnosisKey.builder() + final DiagnosisKey key = DiagnosisKey.builder() .withKeyDataAndSubmissionType(id, type) .withRollingStartIntervalNumber(600) .withTransmissionRiskLevel(2) @@ -58,15 +109,8 @@ void shouldCheckExistence() { assertTrue(repository.exists(id, type.name())); } - @ParameterizedTest - @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) - void recordSrsTest(final SubmissionType type) { - assertTrue(repository.recordSrs(type.name())); - assertEquals(1, repository.countTodaysSrs()); - LocalDate tomorrow = LocalDate.now().plusDays(1); - assertEquals(1, repository.countSrsOlderThan(tomorrow)); - repository.deleteSrsOlderThan(tomorrow); - assertEquals(0, repository.countTodaysSrs()); - assertEquals(0, repository.countSrsOlderThan(tomorrow)); + @AfterEach + void tearDown() { + repository.deleteAll(); } } From 97794f76a6eae69095483c6bddc41fd6750c7edc Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 8 Dec 2022 16:56:44 +0100 Subject: [PATCH 48/83] DiagnosisKeyService + exists(Collection) --- .../service/DiagnosisKeyService.java | 57 ++++++++++++------- .../SubmissionServiceTrlMappingTest.java | 46 ++++++++++++--- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java index 947d18c218..19eabf0aa3 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java @@ -1,6 +1,7 @@ package app.coronawarn.server.common.persistence.service; import static app.coronawarn.server.common.persistence.domain.validation.ValidSubmissionTimestampValidator.SECONDS_PER_HOUR; +import static java.time.LocalDateTime.ofInstant; import static java.time.ZoneOffset.UTC; import static org.springframework.data.util.StreamUtils.createStreamFromIterator; @@ -11,7 +12,7 @@ import io.micrometer.core.annotation.Timed; import java.time.Instant; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Collection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,11 +36,7 @@ public static long daysToSeconds(final int daysToRetain) { if (daysToRetain < 0) { throw new IllegalArgumentException("Number of days to retain must be greater or equal to 0."); } - - return LocalDateTime - .ofInstant(Instant.now(), UTC) - .minusDays(daysToRetain) - .toEpochSecond(UTC) / SECONDS_PER_HOUR; + return ofInstant(Instant.now(), UTC).minusDays(daysToRetain).toEpochSecond(UTC) / SECONDS_PER_HOUR; } private final DiagnosisKeyRepository keyRepository; @@ -58,34 +55,59 @@ public DiagnosisKeyService(final DiagnosisKeyRepository keyRepository, final Val * @param daysToRetain the number of days until which diagnosis keys will be retained. * @throws IllegalArgumentException if {@code daysToRetain} is negative. */ + @Timed @Transactional public void applyRetentionPolicy(final int daysToRetain) { final long threshold = daysToSeconds(daysToRetain); final int numberOfDeletions = keyRepository.countOlderThan(threshold); - logger.info("Deleting {} diagnosis key(s) with a submission timestamp older than {} day(s) ago.", - numberOfDeletions, daysToRetain); + logger.info("Deleting {} diagnosis key(s) with a submission timestamp older than {} day(s) ago.", numberOfDeletions, + daysToRetain); keyRepository.deleteOlderThan(threshold); } /** * Delete entries from 'self_report_submissions', which are older than given days. - * + * * @param retentionDays - How many days should be kept in DB? */ + @Timed @Transactional public void applySrsRetentionPolicy(final int retentionDays) { final LocalDate retentionDate = LocalDate.now(UTC).minusDays(retentionDays); final int numberOfDeletions = keyRepository.countSrsOlderThan(retentionDate); - logger.info("Deleting {} SRS with a submission date older than {} day(s) ago.", - numberOfDeletions, retentionDays); + logger.info("Deleting {} SRS with a submission date older than {} day(s) ago.", numberOfDeletions, retentionDays); keyRepository.deleteSrsOlderThan(retentionDate); } + @Timed + @Transactional + public int countTodaysSrs() { + return keyRepository.countTodaysSrs(); + } + + /** + * Check if any of the key data is already stored in the DB, regardless of submission type. + * + * @param keys to be checked + * @return true if one or more keys are already persisted. + */ + @Timed + @Transactional + public boolean exists(final Collection keys) { + final Collection data = new ArrayList<>(keys.size()); + for (final DiagnosisKey key : keys) { + data.add(key.getKeyData()); + } + return keyRepository.exists(data); + } + /** * Returns all valid persisted diagnosis keys, sorted by their submission timestamp. * * @return ValidDiagnosisKeyFilter */ + @Timed + @Transactional public Collection getDiagnosisKeys() { final Collection diagnosisKeys = createStreamFromIterator( keyRepository.findAll(Sort.by(Direction.ASC, "submissionTimestamp")).iterator()).toList(); @@ -99,6 +121,8 @@ public Collection getDiagnosisKeys() { * @param daysToFetch time in days, that should be published * @return List of {@link DiagnosisKey}s filtered by {@link #validationFilter}. */ + @Timed + @Transactional public Collection getDiagnosisKeysWithMinTrl(final int minTrl, final int daysToFetch) { final Collection diagnosisKeys = keyRepository.findAllWithTrlGreaterThanOrEqual(minTrl, daysToSeconds(daysToFetch)); @@ -111,12 +135,6 @@ public boolean recordSrs(final SubmissionType submissionType) { return keyRepository.recordSrs(submissionType.name()); } - @Timed - @Transactional - public int countTodaysSrs() { - return keyRepository.countTodaysSrs(); - } - /** * Persists the specified collection of {@link DiagnosisKey} instances and returns the number of inserted diagnosis * keys. If the key data of a particular diagnosis key already exists in the database and is of a submission type that @@ -131,13 +149,10 @@ public int countTodaysSrs() { @Transactional public int saveDiagnosisKeys(final Collection diagnosisKeys) { int numberOfInsertedKeys = 0; - for (final DiagnosisKey diagnosisKey : diagnosisKeys) { - if (keyRepository.exists(diagnosisKey.getKeyData(), SubmissionType.SUBMISSION_TYPE_PCR_TEST.name())) { continue; } - final boolean keyInsertedSuccessfully = keyRepository.saveDoNothingOnConflict( diagnosisKey.getKeyData(), diagnosisKey.getRollingStartIntervalNumber(), diagnosisKey.getRollingPeriod(), diagnosisKey.getSubmissionTimestamp(), diagnosisKey.getTransmissionRiskLevel(), @@ -149,13 +164,11 @@ public int saveDiagnosisKeys(final Collection diagnosisKeys) { numberOfInsertedKeys++; } } - final int conflictingKeys = diagnosisKeys.size() - numberOfInsertedKeys; if (conflictingKeys > 0) { logger.warn("{} out of {} diagnosis keys conflicted with existing database entries and were ignored.", conflictingKeys, diagnosisKeys.size()); } - return numberOfInsertedKeys; } } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java index 0799747930..fb63e07af1 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/config/SubmissionServiceTrlMappingTest.java @@ -3,6 +3,7 @@ import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildMultipleKeys; import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildPayload; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.springframework.http.HttpStatus.OK; @@ -10,11 +11,15 @@ import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.persistence.domain.config.TrlDerivations; import app.coronawarn.server.common.persistence.service.DiagnosisKeyService; +import app.coronawarn.server.common.protocols.external.exposurenotification.ReportType; +import app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType; import app.coronawarn.server.services.submission.controller.RequestExecutor; import app.coronawarn.server.services.submission.verification.TanVerifier; import java.util.Collection; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Random; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +30,22 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class SubmissionServiceTrlMappingTest { + public static DiagnosisKey randomKey() { + final SubmissionType type = SubmissionType.SUBMISSION_TYPE_PCR_TEST; + final byte[] id = new byte[16]; + new Random().nextBytes(id); + + return DiagnosisKey.builder() + .withKeyDataAndSubmissionType(id, type) + .withRollingStartIntervalNumber(600) + .withTransmissionRiskLevel(2) + .withRollingPeriod(1) + .withSubmissionTimestamp(0L) + .withReportType(ReportType.CONFIRMED_TEST) + .withCountryCode("DE") + .build(); + } + @Autowired private DiagnosisKeyService diagnosisKeyService; @@ -37,15 +58,21 @@ class SubmissionServiceTrlMappingTest { @MockBean private TanVerifier tanVerifier; - @BeforeEach - void setupMocks() { - when(tanVerifier.verifyTan(anyString())).thenReturn(true); + @Test + void checkKeyDataExistsWithHugeCollection() { + final Collection keys = new LinkedList<>(); + for (int i = 0; i < config.getMaxNumberOfKeys(); i++) { + keys.add(randomKey()); + keys.add(randomKey()); + } + final int saved = diagnosisKeyService.saveDiagnosisKeys(keys); + assertTrue(saved > config.getMaxNumberOfKeys(), "didn't save more keys, than allowed?"); + assertTrue(diagnosisKeyService.exists(keys)); } @Test void checkResponseStatusForTrlMappingLoadingCorrectly() { - Map expectedValues = Map.of(1, 1, 2, 2, 3, 3, 4, 4, - 5, 5, 6, 6, 7, 7, 8, 8); + final Map expectedValues = Map.of(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8); final ResponseEntity actResponse = executor.executePost(buildPayload(buildMultipleKeys(config))); assertThat(config.getTrlDerivations().getTrlMapping()).containsAllEntriesOf(expectedValues); assertThat(actResponse.getStatusCode()).isEqualTo(OK); @@ -55,7 +82,7 @@ void checkResponseStatusForTrlMappingLoadingCorrectly() { void checkTrlMappingDerivation() { // buildMultipleKeys(config) creates keys with TRL 3, 6, 8. // The mappings for these values can be found in test/resources(3, 6, 8) - Collection expectedTrlValues = List.of(3, 6, 8); + final Collection expectedTrlValues = List.of(3, 6, 8); final ResponseEntity actResponse = executor.executePost(buildPayload(buildMultipleKeys(config))); final Collection diagnosisKeyList = diagnosisKeyService.getDiagnosisKeys(); @@ -65,9 +92,14 @@ void checkTrlMappingDerivation() { assertThat(actResponse.getStatusCode()).isEqualTo(OK); } + @BeforeEach + void setupMocks() { + when(tanVerifier.verifyTan(anyString())).thenReturn(true); + } + @Test void testSubmissionServiceConfigValuesAreSet() { - TrlDerivations trlDerivations = new TrlDerivations(); + final TrlDerivations trlDerivations = new TrlDerivations(); trlDerivations.setTrlMapping(Map.of(1, 1, 2, 2, 3, 3, 4, 4)); config.setTrlDerivations(trlDerivations); From 7fcc35149b8757f60e1ad26ddb70861b71a71ff0 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 8 Dec 2022 16:59:39 +0100 Subject: [PATCH 49/83] there is only one: Marker SECURITY --- .../checkins/EventCheckInProtectedReportsValidator.java | 6 ++---- .../submission/checkins/EventCheckinDataValidator.java | 5 ++--- .../submission/controller/SubmissionController.java | 3 +++ .../validation/ValidSubmissionOnBehalfPayload.java | 4 +--- .../services/submission/verification/EventTanVerifier.java | 2 ++ .../submission/verification/TanVerificationService.java | 4 ---- .../services/submission/verification/TanVerifier.java | 4 ++-- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckInProtectedReportsValidator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckInProtectedReportsValidator.java index 3efd544706..866d63d15c 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckInProtectedReportsValidator.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckInProtectedReportsValidator.java @@ -1,13 +1,13 @@ package app.coronawarn.server.services.submission.checkins; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SECURITY; + import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import app.coronawarn.server.common.protocols.internal.pt.CheckInProtectedReport; import java.util.List; import javax.validation.ConstraintValidatorContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; @@ -19,9 +19,7 @@ public class EventCheckInProtectedReportsValidator { public static final int MAC_LENGTH = 32; public static final int ENCRYPTED_CHECK_IN_RECORD_LENGTH = 16; - private static final Logger logger = LoggerFactory.getLogger(EventCheckInProtectedReportsValidator.class); - private static final Marker SECURITY = MarkerFactory.getMarker("SECURITY"); /** * Given the submission payload, it verifies whether user event checkInProtectedReports data is aligned with the diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckinDataValidator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckinDataValidator.java index 7797f0ba7d..511c692df3 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckinDataValidator.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/checkins/EventCheckinDataValidator.java @@ -1,5 +1,7 @@ package app.coronawarn.server.services.submission.checkins; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SECURITY; + import app.coronawarn.server.common.persistence.domain.DiagnosisKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import app.coronawarn.server.common.protocols.internal.pt.CheckIn; @@ -7,8 +9,6 @@ import javax.validation.ConstraintValidatorContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; @@ -22,7 +22,6 @@ public class EventCheckinDataValidator { private static final Logger logger = LoggerFactory.getLogger(EventCheckinDataValidator.class); - private static final Marker SECURITY = MarkerFactory.getMarker("SECURITY"); /** * Given the submission payload, it verifies whether user event checkin data is aligned with the application diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 2806fe7d70..4299ab4041 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -36,6 +36,8 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity.BodyBuilder; @@ -62,6 +64,7 @@ public class SubmissionController { public static final String CWA_FILTERED_CHECKINS_HEADER = "cwa-filtered-checkins"; public static final String CWA_SAVED_CHECKINS_HEADER = "cwa-saved-checkins"; public static final String CWA_KEYS_TRUNCATED_HEADER = "cwa-keys-truncated"; + public static final Marker SECURITY = MarkerFactory.getMarker("SECURITY"); private final SubmissionMonitor submissionMonitor; private final DiagnosisKeyService diagnosisKeyService; diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionOnBehalfPayload.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionOnBehalfPayload.java index 297fd4c559..28669f0ddc 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionOnBehalfPayload.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/ValidSubmissionOnBehalfPayload.java @@ -1,5 +1,6 @@ package app.coronawarn.server.services.submission.validation; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SECURITY; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -17,8 +18,6 @@ import javax.validation.Payload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; @SuppressWarnings("deprecation") @Target(PARAMETER) @@ -58,7 +57,6 @@ class ValidSubmissionOnBehalfPayloadValidator implements private final EventCheckinDataValidator eventCheckInValidator; private final EventCheckInProtectedReportsValidator eventCheckInProtectedReportsValidator; private static final Logger logger = LoggerFactory.getLogger(ValidSubmissionOnBehalfPayload.class); - private static final Marker SECURITY = MarkerFactory.getMarker("SECURITY"); public ValidSubmissionOnBehalfPayloadValidator(EventCheckinDataValidator eventCheckinValidator, EventCheckInProtectedReportsValidator eventCheckInProtectedReportsValidator) { diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/EventTanVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/EventTanVerifier.java index 2723d5d4bf..03e735399e 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/EventTanVerifier.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/EventTanVerifier.java @@ -1,5 +1,7 @@ package app.coronawarn.server.services.submission.verification; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SECURITY; + import feign.FeignException; import java.util.List; import java.util.Optional; diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerificationService.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerificationService.java index 44f2cd3b92..d8fd6b2d02 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerificationService.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerificationService.java @@ -2,16 +2,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; import org.springframework.web.client.RestClientException; public abstract class TanVerificationService { public static final String CWA_TELETAN_TYPE_RESPONSE_HEADER = "X-CWA-TELETAN-TYPE"; public static final String CWA_TELETAN_TYPE_EVENT = "EVENT"; - protected static final Marker SECURITY = MarkerFactory.getMarker("SECURITY"); - private static final Logger logger = LoggerFactory.getLogger(TanVerificationService.class); final VerificationServerClient verificationServerClient; diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java index 672955612c..e18999af53 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/TanVerifier.java @@ -1,7 +1,7 @@ - - package app.coronawarn.server.services.submission.verification; +import static app.coronawarn.server.services.submission.controller.SubmissionController.SECURITY; + import feign.FeignException; import java.util.List; import java.util.Optional; From a3c3721146e02754b335c07dace62c1b5a51fe3b Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 8 Dec 2022 16:59:58 +0100 Subject: [PATCH 50/83] add submission type --- .../PrintableSubmissionPayload.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/PrintableSubmissionPayload.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/PrintableSubmissionPayload.java index 242eff67e4..6c0c24244c 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/PrintableSubmissionPayload.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/validation/PrintableSubmissionPayload.java @@ -10,35 +10,36 @@ public class PrintableSubmissionPayload { - private final String origin; - private final ProtocolStringList visitedCountries; - private final boolean consentToFederation; - private final ByteString padding; - private final List keys; - - static final String ORIGIN_MESSAGE = " payload origin: "; - static final String VISITED_COUNTRIES_MESSAGE = " visited_countries: "; static final String CONSENT_MESSAGE = " consent_to_federation: "; + static final String ORIGIN_MESSAGE = " payload origin: "; static final String PADDING_MESSAGE = " with padding_size: "; + static final String VISITED_COUNTRIES_MESSAGE = " visited_countries: "; + private final boolean consentToFederation; + private final List keys; + private final String origin; + private final ByteString padding; + private final String submissionType; + private final ProtocolStringList visitedCountries; /** * Creates a printable Version of SubmissionPayload the logger can work with. - * + * * @param submissionPayload SubmissionPayload which shall be made printable */ - public PrintableSubmissionPayload(SubmissionPayload submissionPayload) { + public PrintableSubmissionPayload(final SubmissionPayload submissionPayload) { origin = submissionPayload.getOrigin(); visitedCountries = submissionPayload.getVisitedCountriesList(); consentToFederation = submissionPayload.getConsentToFederation(); padding = submissionPayload.getRequestPadding(); keys = submissionPayload.getKeysList(); + submissionType = submissionPayload.getSubmissionType().name(); } @Override public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - + final StringBuilder stringBuilder = new StringBuilder(); stringBuilder + .append("submission type: ").append(submissionType) .append(ORIGIN_MESSAGE).append(origin) .append(VISITED_COUNTRIES_MESSAGE).append(visitedCountries) .append(CONSENT_MESSAGE).append(consentToFederation) @@ -48,7 +49,7 @@ public String toString() { } else { stringBuilder.append(" " + keys.size() + " keys: "); } - for (TemporaryExposureKey key : keys) { + for (final TemporaryExposureKey key : keys) { stringBuilder .append("{") .append("data: HIDDEN") @@ -60,5 +61,4 @@ public String toString() { } return stringBuilder.toString(); } - } From 830efac5a80d1cc881657c1257904f5768b0e9c8 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 8 Dec 2022 17:01:25 +0100 Subject: [PATCH 51/83] SRS - throw DiagnosisKeyExistsAlreadyException if keydata is already in DB respond with 400/badRequest --- .../DiagnosisKeyExistsAlreadyException.java | 5 ++++ .../controller/SubmissionController.java | 24 +++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java new file mode 100644 index 0000000000..bcb65e3aa9 --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java @@ -0,0 +1,5 @@ +package app.coronawarn.server.services.submission.controller; + +final class DiagnosisKeyExistsAlreadyException extends Exception { + private static final long serialVersionUID = 6692058450561715150L; +} diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 4299ab4041..06120f5508 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -123,7 +123,7 @@ public DeferredResult> submitDiagnosisKey( } if (!isEmpty(otp)) { - if (!validSrsType(exposureKeys)) { + if (!isSelfReport(exposureKeys)) { return badRequest(); } if (diagnosisKeyService.countTodaysSrs() >= submissionServiceConfig.getMaxSrsPerDay()) { @@ -169,7 +169,7 @@ private boolean validSubmissionType(final SubmissionPayload payload) { * * @see SubmissionType */ - private boolean validSrsType(final SubmissionPayload payload) { + private boolean isSelfReport(final SubmissionPayload payload) { return SUBMISSION_TYPE_SRS_SELF_TEST_VALUE <= payload.getSubmissionType().getNumber() && payload.getSubmissionType().getNumber() <= SUBMISSION_TYPE_SRS_OTHER_VALUE; } @@ -214,8 +214,8 @@ public DeferredResult> submissionOnBehalf( * @param tan A tan for diagnosis verification. * @return DeferredResult. */ - private DeferredResult> buildRealDeferredResult(SubmissionPayload submissionPayload, - String tan, TanVerificationService tanVerifier) { + private DeferredResult> buildRealDeferredResult(final SubmissionPayload submissionPayload, + final String tan, final TanVerificationService tanVerifier) { DeferredResult> deferredResult = new DeferredResult<>(); StopWatch stopWatch = new StopWatch(); @@ -230,7 +230,7 @@ private DeferredResult> buildRealDeferredResult(SubmissionP CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(submissionPayload); - if (validSrsType(submissionPayload)) { + if (isSelfReport(submissionPayload)) { diagnosisKeyService.recordSrs(submissionPayload.getSubmissionType()); } @@ -244,6 +244,10 @@ private DeferredResult> buildRealDeferredResult(SubmissionP } catch (FeignException e) { logger.error("Verification Service could not be reached.", e); deferredResult.setErrorResult(e); + } catch (DiagnosisKeyExistsAlreadyException e) { + logger.warn(SECURITY, "Self-Report contains already persisted keys - {}", + new PrintableSubmissionPayload(submissionPayload)); + deferredResult.setErrorResult(ResponseEntity.badRequest()); } catch (Exception e) { deferredResult.setErrorResult(e); } finally { @@ -253,9 +257,15 @@ private DeferredResult> buildRealDeferredResult(SubmissionP return deferredResult; } - private void extractAndStoreDiagnosisKeys(final SubmissionPayload submissionPayload, final BodyBuilder response) { + private void extractAndStoreDiagnosisKeys(final SubmissionPayload submissionPayload, final BodyBuilder response) + throws DiagnosisKeyExistsAlreadyException { final Collection diagnosisKeys = extractValidDiagnosisKeysFromPayload( enhanceWithDefaultValuesIfMissing(submissionPayload), response); + + if (isSelfReport(submissionPayload) && diagnosisKeyService.exists(diagnosisKeys)) { + throw new DiagnosisKeyExistsAlreadyException(); + } + for (final DiagnosisKey diagnosisKey : diagnosisKeys) { mapTrasmissionRiskValue(diagnosisKey); } @@ -286,7 +296,7 @@ private Collection extractValidDiagnosisKeysFromPayload(final Subm .filter(diagnosisKey -> diagnosisKey.isYoungerThanRetentionThreshold(retentionDays)) .toList(); - if (validSrsType(submissionPayload)) { + if (isSelfReport(submissionPayload)) { final Collection keys = diagnosisKeys.stream() .filter(diagnosisKey -> diagnosisKey.isYoungerThanRetentionThreshold(srsDays)).toList(); if (keys.size() < diagnosisKeys.size()) { From aea8bfe4265e52668075d747da5dd215a404b00f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 9 Dec 2022 14:37:11 +0100 Subject: [PATCH 52/83] really return 400 - 'BAD REQUEST' and not http 500 --- .../controller/SubmissionController.java | 18 +++++----- .../controller/SubmissionControllerTest.java | 36 ++++++++++++------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 06120f5508..9a3c208313 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -210,11 +210,11 @@ public DeferredResult> submissionOnBehalf( /** * Saves the checkins and, if needed, filters them. * - * @param submissionPayload Type protobuf. + * @param payload Type protobuf. * @param tan A tan for diagnosis verification. * @return DeferredResult. */ - private DeferredResult> buildRealDeferredResult(final SubmissionPayload submissionPayload, + private DeferredResult> buildRealDeferredResult(final SubmissionPayload payload, final String tan, final TanVerificationService tanVerifier) { DeferredResult> deferredResult = new DeferredResult<>(); @@ -226,12 +226,12 @@ private DeferredResult> buildRealDeferredResult(final Submi deferredResult.setResult(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); } else { final BodyBuilder response = ResponseEntity.ok(); - extractAndStoreDiagnosisKeys(submissionPayload, response); + extractAndStoreDiagnosisKeys(payload, response); - CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(submissionPayload); + CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(payload); - if (isSelfReport(submissionPayload)) { - diagnosisKeyService.recordSrs(submissionPayload.getSubmissionType()); + if (isSelfReport(payload)) { + diagnosisKeyService.recordSrs(payload.getSubmissionType()); } response.header(CWA_FILTERED_CHECKINS_HEADER, valueOf(checkinsStorageResult.getNumberOfFilteredCheckins())) @@ -244,10 +244,10 @@ private DeferredResult> buildRealDeferredResult(final Submi } catch (FeignException e) { logger.error("Verification Service could not be reached.", e); deferredResult.setErrorResult(e); - } catch (DiagnosisKeyExistsAlreadyException e) { + } catch (final DiagnosisKeyExistsAlreadyException e) { logger.warn(SECURITY, "Self-Report contains already persisted keys - {}", - new PrintableSubmissionPayload(submissionPayload)); - deferredResult.setErrorResult(ResponseEntity.badRequest()); + new PrintableSubmissionPayload(payload)); + return badRequest(); } catch (Exception e) { deferredResult.setErrorResult(e); } finally { 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 452f8decc0..6294519fa5 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 @@ -21,6 +21,8 @@ import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildSrsPayload; import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.buildTemporaryExposureKey; import static app.coronawarn.server.services.submission.controller.SubmissionPayloadMockData.createRollingStartIntervalNumber; +import static java.lang.String.valueOf; +import static java.time.LocalDateTime.ofInstant; import static java.time.ZoneOffset.UTC; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.assertj.core.api.Assertions.assertThat; @@ -56,7 +58,6 @@ import app.coronawarn.server.services.submission.verification.TanVerifier; import com.google.protobuf.ByteString; import java.time.Instant; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -434,8 +435,8 @@ void submmissionPayloadWithInvalidIntervalItemsIsFilteredCorrectly() { @Test void testCheckinDataIsFilteredForFutureEvents() { final Instant thisInstant = Instant.now(); - final long eventCheckinInTheFuture = LocalDateTime.ofInstant(thisInstant, UTC).plusMinutes(11).toEpochSecond(UTC); - final long eventCheckoutInTheFuture = LocalDateTime.ofInstant(thisInstant, UTC).plusMinutes(20) + final long eventCheckinInTheFuture = ofInstant(thisInstant, UTC).plusMinutes(11).toEpochSecond(UTC); + final long eventCheckoutInTheFuture = ofInstant(thisInstant, UTC).plusMinutes(20) .toEpochSecond(UTC); final List checkins = List @@ -452,9 +453,9 @@ void testCheckinDataIsFilteredForFutureEvents() { void testCheckinDataIsFilteredForOldEvents() { final Integer daysInThePast = config.getAcceptedEventDateThresholdDays() + 1; final Instant thisInstant = Instant.now(); - final long eventCheckoutInThePast = LocalDateTime.ofInstant(thisInstant, UTC).minusDays(daysInThePast) + final long eventCheckoutInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast) .toEpochSecond(UTC); - final long eventCheckinInThePast = LocalDateTime.ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1) + final long eventCheckinInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1) .toEpochSecond(UTC); final List checkins = List @@ -471,12 +472,12 @@ void testCheckinDataIsFilteredForOldEvents() { void testCheckinDataHeadersAreCorrectlyFilled() { final Integer daysInThePast = config.getAcceptedEventDateThresholdDays() + 1; final Instant thisInstant = Instant.now(); - final long eventCheckoutInThePast = LocalDateTime.ofInstant(thisInstant, UTC).minusDays(daysInThePast) + final long eventCheckoutInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast) .toEpochSecond(UTC); - final long eventCheckinInThePast = LocalDateTime.ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1) + final long eventCheckinInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1) .toEpochSecond(UTC); - final long eventCheckinInAllowedPeriod = LocalDateTime.ofInstant(Instant.now(), UTC).minusDays(10) + final long eventCheckinInAllowedPeriod = ofInstant(Instant.now(), UTC).minusDays(10) .toEpochSecond(UTC); final List checkins = List @@ -496,7 +497,7 @@ void testCheckinDataHeadersAreCorrectlyFilled() { @Test void testCheckinDataIsFilteredForTransmissionRiskLevel() { - final long eventCheckinInThePast = LocalDateTime.ofInstant(Instant.now(), UTC).minusDays(10).toEpochSecond(UTC); + final long eventCheckinInThePast = ofInstant(Instant.now(), UTC).minusDays(10).toEpochSecond(UTC); // both trls below are mapped to zero in the persistence/trl-value-mapping.yaml final List invalidCheckinData = List.of( @@ -573,7 +574,7 @@ void testMissingOriginCountrySubmissionPayload() { @Test void testValidCheckinData() { - final long eventCheckinInThePast = LocalDateTime.ofInstant(Instant.now(), UTC).minusDays(9).toEpochSecond(UTC); + final long eventCheckinInThePast = ofInstant(Instant.now(), UTC).minusDays(9).toEpochSecond(UTC); final List validCheckinData = List.of( CheckIn.newBuilder().setTransmissionRiskLevel(3) @@ -658,7 +659,18 @@ void testSrsKeysTruncated() { buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); assertThat(response.getStatusCode()).isEqualTo(OK); assertThat(response.getHeaders()).containsKey(CWA_KEYS_TRUNCATED_HEADER); - assertThat(response.getHeaders()).containsEntry(CWA_KEYS_TRUNCATED_HEADER, - List.of(String.valueOf(config.getSrsDays()))); + assertThat(response.getHeaders()).containsEntry(CWA_KEYS_TRUNCATED_HEADER, List.of(valueOf(config.getSrsDays()))); + } + + /** + * @see SubmissionController#extractAndStoreDiagnosisKeys(SubmissionPayload, BodyBuilder) + */ + @Test + void testSrsKeysSendTwice() { + // fake existing keys + when(diagnosisKeyService.exists(any())).thenReturn(true); + // send again + ResponseEntity response = executor.executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } } From 21c1c46144e7af1428c62db19545e0a38ad4e811 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 9 Dec 2022 14:41:03 +0100 Subject: [PATCH 53/83] exclude outdated bouncycastle:bcpkix-jdk15on --- services/pom.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/pom.xml b/services/pom.xml index 1ff9bb1c5d..f6ecc57f3d 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -23,10 +23,16 @@ - + org.springframework.cloud spring-cloud-starter-bootstrap + + + org.bouncycastle + bcpkix-jdk15on + + org.springframework.cloud From e41e0a6b30c46bae2849f0f8c736b09a8ee6b191 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 9 Dec 2022 15:07:08 +0100 Subject: [PATCH 54/83] log errors --- .../services/submission/controller/SubmissionController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 9a3c208313..bdb9c2352e 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -249,6 +249,7 @@ private DeferredResult> buildRealDeferredResult(final Submi new PrintableSubmissionPayload(payload)); return badRequest(); } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); deferredResult.setErrorResult(e); } finally { stopWatch.stop(); From 5afd8cf875a8f9dd2c0dea29d9f13ed6c8393c95 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 9 Dec 2022 15:07:42 +0100 Subject: [PATCH 55/83] prevent BAD SQL grammar exception --- .../common/persistence/service/DiagnosisKeyService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java index 19eabf0aa3..6aa6050d53 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; @Component public class DiagnosisKeyService { @@ -94,6 +95,9 @@ public int countTodaysSrs() { @Timed @Transactional public boolean exists(final Collection keys) { + if (ObjectUtils.isEmpty(keys)) { + return false; + } final Collection data = new ArrayList<>(keys.size()); for (final DiagnosisKey key : keys) { data.add(key.getKeyData()); From afe43c8fd3f3c8da6c5e9e55440f8a5cbefb978d Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 08:43:31 +0100 Subject: [PATCH 56/83] exclude bcpkix-jdk15on --- common/federation/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/federation/pom.xml b/common/federation/pom.xml index fa181405db..749b700301 100644 --- a/common/federation/pom.xml +++ b/common/federation/pom.xml @@ -23,6 +23,12 @@ org.springframework.cloud spring-cloud-starter-openfeign + + + org.bouncycastle + bcpkix-jdk15on + + io.github.openfeign From 223e21c6e8fb41bb6208c33d1875c5f43dd6bf0d Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 11:22:21 +0100 Subject: [PATCH 57/83] HealthIndicator logging --- .../monitoring/SrsVerifyServiceHealthIndicator.java | 6 ++++++ .../monitoring/VerificationServiceHealthIndicator.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java index f0790b6910..9c04afe2ce 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/SrsVerifyServiceHealthIndicator.java @@ -4,6 +4,8 @@ import app.coronawarn.server.services.submission.verification.SrsVerifyClient; import app.coronawarn.server.services.submission.verification.Tan; import feign.FeignException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @@ -16,6 +18,8 @@ @Component("srsVerifyService") public class SrsVerifyServiceHealthIndicator implements HealthIndicator { + private static final Logger logger = LoggerFactory.getLogger(SrsVerifyServiceHealthIndicator.class); + private final SrsVerifyClient client; SrsVerifyServiceHealthIndicator(final SrsVerifyClient client) { @@ -28,8 +32,10 @@ public Health health() { client.verifyOtp(Otp.of(Tan.of("00000000-0000-0000-0000-000000000000"))); } catch (final FeignException.NotFound e) { // expected + logger.info(e.getLocalizedMessage()); } catch (final Exception e) { // http status code is neither 2xx nor 404 + logger.error(e.getLocalizedMessage(), e); return Health.down(e).build(); } return Health.up().build(); diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java index e2155e22a7..af50986cdb 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/monitoring/VerificationServiceHealthIndicator.java @@ -3,6 +3,8 @@ import app.coronawarn.server.services.submission.verification.Tan; import app.coronawarn.server.services.submission.verification.VerificationServerClient; import feign.FeignException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @@ -16,6 +18,8 @@ @Component("verificationService") public class VerificationServiceHealthIndicator implements HealthIndicator { + private static final Logger logger = LoggerFactory.getLogger(VerificationServiceHealthIndicator.class); + private final VerificationServerClient verificationServerClient; VerificationServiceHealthIndicator(VerificationServerClient verificationServerClient) { @@ -28,8 +32,10 @@ public Health health() { verificationServerClient.verifyTan(Tan.of("00000000-0000-0000-0000-000000000000")); } catch (FeignException.NotFound e) { // expected + logger.info(e.getLocalizedMessage()); } catch (Exception e) { // http status code is neither 2xx nor 404 + logger.error(e.getLocalizedMessage(), e); return Health.down(e).build(); } return Health.up().build(); From 7c743ecb4b93e67a30e0a47e4f404a66fa7e61de Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 12:25:31 +0100 Subject: [PATCH 58/83] SrsOtpRedemptionResponse --- .../submission/verification/OtpState.java | 25 +++++++++ .../SrsOtpRedemptionResponse.java | 53 +++++++++++++++++++ .../verification/SrsOtpVerifier.java | 12 +++-- .../verification/SrsVerifyClient.java | 2 +- 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/verification/OtpState.java create mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/OtpState.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/OtpState.java new file mode 100644 index 0000000000..46ec7b30b9 --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/OtpState.java @@ -0,0 +1,25 @@ +package app.coronawarn.server.services.submission.verification; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum OtpState { + EXPIRED("expired"), + REDEEMED("redeemed"), + VALID("valid"); + + private final String state; + + OtpState(final String state) { + this.state = state; + } + + @JsonValue + final String state() { + return state; + } + + @Override + public String toString() { + return state; + } +} diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java new file mode 100644 index 0000000000..82b0216ef4 --- /dev/null +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java @@ -0,0 +1,53 @@ +package app.coronawarn.server.services.submission.verification; + +public class SrsOtpRedemptionResponse { + + private String otp; + + private OtpState state; + + private boolean strongClientIntegrityCheck; + + /** + * Constructor. + * + * @param otp The SRS one time password . + * @param state The SRS OTP state. + * @param strongClientIntegrityCheck The strongClientIntegrityCheck. + */ + public SrsOtpRedemptionResponse(final String otp, final OtpState state, final boolean strongClientIntegrityCheck) { + this.otp = otp; + this.state = state; + this.strongClientIntegrityCheck = strongClientIntegrityCheck; + } + + public String getOtp() { + return otp; + } + + public OtpState getState() { + return state; + } + + public boolean isStrongClientIntegrityCheck() { + return strongClientIntegrityCheck; + } + + public void setOtp(final String otp) { + this.otp = otp; + } + + public void setState(final OtpState state) { + this.state = state; + } + + public void setStrongClientIntegrityCheck(final boolean strongClientIntegrityCheck) { + this.strongClientIntegrityCheck = strongClientIntegrityCheck; + } + + @Override + public String toString() { + return "{\"otp\":\"" + otp + "\",\"state\":\"" + state + "\",\"strongClientIntegrityCheck\":" + + strongClientIntegrityCheck + "}"; + } +} diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java index 195a2f66b4..16f3ecd2da 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java @@ -26,13 +26,15 @@ boolean verifyWithVerificationService(final Tan tan) { try { logger.debug("Calling SRS OPT verification Service ..."); - final ResponseEntity result = client.verifyOtp(Otp.of(tan)); - logger.debug("Received response from SRS-verify: {}", result); - - return true; + final ResponseEntity result = client.verifyOtp(Otp.of(tan)); + logger.info("Received response from SRS-verify: {}", result); + if (result.getStatusCode().is2xxSuccessful() && OtpState.VALID.equals(result.getBody().getState())) { + return true; + } + logger.warn("SRS-verify reponse: '{}'", result.getBody()); } catch (final FeignException.NotFound e) { logger.warn("SRS OTP verification service reported: NotFound", e); - return false; } + return false; } } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java index da9f35f412..f033c629d7 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsVerifyClient.java @@ -22,5 +22,5 @@ public interface SrsVerifyClient { */ @Timed @PostMapping(value = "${services.submission.srs-verify.path}", consumes = MediaType.APPLICATION_JSON_VALUE) - ResponseEntity verifyOtp(final Otp otp); + ResponseEntity verifyOtp(final Otp otp); } From 33dbe0772d3c82472200840467736b3ce7a55a03 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 14:51:21 +0100 Subject: [PATCH 59/83] JSON desirialization of SrsOtpRedemptionResponse with com.fasterxml.jackson.core.JsonParser --- .../SrsOtpRedemptionResponse.java | 28 ++++++ .../verification/SrsVerifierTest.java | 88 ++++++++++++------- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java index 82b0216ef4..579c5775c7 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java @@ -1,5 +1,9 @@ package app.coronawarn.server.services.submission.verification; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; + public class SrsOtpRedemptionResponse { private String otp; @@ -8,6 +12,26 @@ public class SrsOtpRedemptionResponse { private boolean strongClientIntegrityCheck; + /** + * Required for automatic JSON deserialization. + */ + public SrsOtpRedemptionResponse() { + } + + /** + * Required for automatic JSON deserialization. + * + * @param json - {@link String} representation of this class. See {@link #toString()}. + * @throws IOException when there is an issue with {@link ObjectMapper} or {@link JsonParser#readValueAs(Class)} + */ + public SrsOtpRedemptionResponse(final String json) throws IOException { + final SrsOtpRedemptionResponse me = new ObjectMapper().createParser(json) + .readValueAs(SrsOtpRedemptionResponse.class); + otp = me.otp; + state = me.state; + strongClientIntegrityCheck = me.strongClientIntegrityCheck; + } + /** * Constructor. * @@ -41,6 +65,10 @@ public void setState(final OtpState state) { this.state = state; } + public void setState(final String state) { + this.state = state == null ? null : OtpState.valueOf(state.toUpperCase()); + } + public void setStrongClientIntegrityCheck(final boolean strongClientIntegrityCheck) { this.strongClientIntegrityCheck = strongClientIntegrityCheck; } diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java index 6fa1f072e5..834f614245 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java @@ -1,5 +1,6 @@ package app.coronawarn.server.services.submission.verification; +import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.okForJson; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; @@ -10,6 +11,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.Assert.assertEquals; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; import com.github.tomakehurst.wiremock.WireMockServer; @@ -19,33 +24,45 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; 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.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; @SpringBootTest @DirtiesContext class SrsVerifierTest { - private static WireMockServer server; + private static WireMockServer srsVerifyMockServer; + + static final String TOKEN = "otp"; + + /** + * JSON string representation of new {@link SrsOtpRedemptionResponse}. + * + * @param otp used in {@link SrsOtpRedemptionResponse} + * @param state used in {@link SrsOtpRedemptionResponse} + * @return JSON string + */ + public static String response(final String otp, final OtpState state) { + return new SrsOtpRedemptionResponse(otp, state, false).toString(); + } @BeforeAll static void setupWireMock() { - server = new WireMockServer(options().port(1234)); // test/resources/application.yaml - server.start(); + srsVerifyMockServer = new WireMockServer(options().port(1234)); // fixed port as in test/resources/application.yaml + srsVerifyMockServer.start(); } @AfterAll static void tearDown() { - server.stop(); + srsVerifyMockServer.stop(); } - static final String TOKEN = "otp"; - private String path; private String randomUuid; @@ -59,60 +76,67 @@ static void tearDown() { @Autowired private SrsOtpVerifier verifier; - @BeforeEach - void setup() { - path = submissionServiceConfig.getSrsVerifyPath(); - assertEquals("/version/v1/srs", path); - randomUuid = UUID.randomUUID().toString(); - server.resetAll(); - } - @Test void checkInternalServerError() { - server.stubFor( - post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) - .willReturn(aResponse().withStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()))); + srsVerifyMockServer.stubFor(post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(INTERNAL_SERVER_ERROR.value()))); assertThatExceptionOfType(FeignException.class).isThrownBy(() -> verifier.verifyTan(randomUuid)); } @Test void checkInvalidOtp() { - server.stubFor( - post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) - .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + srsVerifyMockServer.stubFor(post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(NOT_FOUND.value()))); assertThat(verifier.verifyTan(randomUuid)).isFalse(); } @Test void checkTimeout() { - server.stubFor( + srsVerifyMockServer.stubFor( post(urlEqualTo(path)) .withRequestBody(matchingJsonPath(TOKEN, equalTo(randomUuid))) - .withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) - .willReturn(aResponse().withStatus(HttpStatus.OK.value()).withFixedDelay(1000))); + .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(OK.value()).withFixedDelay(1000))); assertThatExceptionOfType(FeignException.class).isThrownBy(() -> verifier.verifyTan(randomUuid)); } @Test void checkTooLongOtp() { - server.stubFor( - post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) - .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + srsVerifyMockServer.stubFor(post(urlEqualTo(path)).withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON.toString())) + .willReturn(aResponse().withStatus(NOT_FOUND.value()))); assertThat(verifier.verifyTan(randomUuid + randomUuid)).isFalse(); } + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = "VALID") + void checkUsedOtp(final OtpState state) { + srsVerifyMockServer.stubFor( + post(urlEqualTo(path)) + .withRequestBody(matchingJsonPath(TOKEN, equalTo(randomUuid))) + .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON.toString())) + .willReturn(okForJson(response(randomUuid, state)))); + assertThat(verifier.verifyTan(randomUuid)).isFalse(); + } + @Test void checkValidOtp() { - server.stubFor( + srsVerifyMockServer.stubFor( post(urlEqualTo(path)) .withRequestBody(matchingJsonPath(TOKEN, equalTo(randomUuid))) - .withHeader(CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON.toString())) - .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); - + .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON.toString())) + .willReturn(okForJson(response(randomUuid, OtpState.VALID)))); assertThat(verifier.verifyTan(randomUuid)).isTrue(); } + + @BeforeEach + void setup() { + path = submissionServiceConfig.getSrsVerifyPath(); + assertEquals("/version/v1/srs", path); + randomUuid = UUID.randomUUID().toString(); + srsVerifyMockServer.resetAll(); + } } From 6315a9e78965ecbd940b4b91016e2c62fbb48de8 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 14:55:38 +0100 Subject: [PATCH 60/83] mitigate NullPointerException --- .../submission/verification/SrsOtpVerifier.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java index 16f3ecd2da..07b0a69901 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpVerifier.java @@ -1,5 +1,7 @@ package app.coronawarn.server.services.submission.verification; +import static org.springframework.util.ObjectUtils.isEmpty; + import feign.FeignException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +23,18 @@ public SrsOtpVerifier(final SrsVerifyClient client) { this.client = client; } + boolean isOk(final ResponseEntity response) { + if (isEmpty(response) || isEmpty(response.getStatusCode()) || !response.getStatusCode().is2xxSuccessful()) { + return false; + } + if (isEmpty(response.getBody())) { + logger.error("SRS OTP response body is null, but status code is: {}!?! - '{}'", response.getStatusCode(), + response); + return true; + } + return OtpState.VALID.equals(response.getBody().getState()); + } + @Override boolean verifyWithVerificationService(final Tan tan) { try { @@ -28,7 +42,7 @@ boolean verifyWithVerificationService(final Tan tan) { final ResponseEntity result = client.verifyOtp(Otp.of(tan)); logger.info("Received response from SRS-verify: {}", result); - if (result.getStatusCode().is2xxSuccessful() && OtpState.VALID.equals(result.getBody().getState())) { + if (isOk(result)) { return true; } logger.warn("SRS-verify reponse: '{}'", result.getBody()); From 34b490f1838caa43a3820091aab1d7ae99768b29 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 15:28:18 +0100 Subject: [PATCH 61/83] SrsOtpVerifier test coverage --- .../verification/SrsVerifierTest.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java index 834f614245..634b2baea4 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/verification/SrsVerifierTest.java @@ -9,9 +9,12 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -31,6 +34,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; @SpringBootTest @@ -139,4 +144,21 @@ void setup() { randomUuid = UUID.randomUUID().toString(); srsVerifyMockServer.resetAll(); } + + @Test + void testIsOk() { + final SrsOtpVerifier fixture = new SrsOtpVerifier(null); + assertFalse(fixture.isOk(null)); + assertFalse(fixture.isOk(new ResponseEntity(I_AM_A_TEAPOT) { + @Override + public HttpStatus getStatusCode() { + return null; + } + })); + assertFalse(fixture.isOk(ResponseEntity.notFound().build())); + assertTrue(fixture.isOk(ResponseEntity.noContent().build())); + assertTrue(fixture.isOk(ResponseEntity.ok(new SrsOtpRedemptionResponse(null, OtpState.VALID, false)))); + assertFalse(fixture.isOk(ResponseEntity.ok(new SrsOtpRedemptionResponse(null, OtpState.REDEEMED, false)))); + assertFalse(fixture.isOk(ResponseEntity.ok(new SrsOtpRedemptionResponse(null, OtpState.EXPIRED, false)))); + } } From 9186f29d52b0045deffe3667e38de941aa44edb1 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Tue, 13 Dec 2022 16:34:39 +0100 Subject: [PATCH 62/83] auto close JsonParser --- .../verification/SrsOtpRedemptionResponse.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java index 579c5775c7..cab96707ff 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/SrsOtpRedemptionResponse.java @@ -25,11 +25,12 @@ public SrsOtpRedemptionResponse() { * @throws IOException when there is an issue with {@link ObjectMapper} or {@link JsonParser#readValueAs(Class)} */ public SrsOtpRedemptionResponse(final String json) throws IOException { - final SrsOtpRedemptionResponse me = new ObjectMapper().createParser(json) - .readValueAs(SrsOtpRedemptionResponse.class); - otp = me.otp; - state = me.state; - strongClientIntegrityCheck = me.strongClientIntegrityCheck; + try (final JsonParser parser = new ObjectMapper().createParser(json)) { + final SrsOtpRedemptionResponse me = parser.readValueAs(SrsOtpRedemptionResponse.class); + otp = me.otp; + state = me.state; + strongClientIntegrityCheck = me.strongClientIntegrityCheck; + } } /** From 76288d70f3eb102bdfdf0dc9a9d7db3ef7aecfca Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 11:46:19 +0100 Subject: [PATCH 63/83] set supported-countries for profile integration-test --- .../src/test/resources/application-integration-test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/distribution/src/test/resources/application-integration-test.yaml b/services/distribution/src/test/resources/application-integration-test.yaml index fe1258d2bb..ce136b6992 100644 --- a/services/distribution/src/test/resources/application-integration-test.yaml +++ b/services/distribution/src/test/resources/application-integration-test.yaml @@ -2,6 +2,8 @@ services: distribution: shifting-policy-threshold: 140 + supported-countries: + - DE logging: level: org: From cf1d0e3ddead920be783dd7c8ce38bf62cf4cb31 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 11:46:54 +0100 Subject: [PATCH 64/83] protobuf 3.21.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 237d888b1b..0784f515d6 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 2.3.2 1.3.4 5.7.5 - 3.21.9 + 3.21.12 1.1.1 1.14.1 1.9.4 From 69e242c3d60d730174026f24afad11a9633c42b3 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 13:26:18 +0100 Subject: [PATCH 65/83] Revert "protobuf 3.21.12" This reverts commit cf1d0e3ddead920be783dd7c8ce38bf62cf4cb31. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0784f515d6..237d888b1b 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 2.3.2 1.3.4 5.7.5 - 3.21.12 + 3.21.9 1.1.1 1.14.1 1.9.4 From b83a23b3885219f9913a5db46b4118f1fa2d7c40 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 13:46:36 +0100 Subject: [PATCH 66/83] OTP syntax check for valid UUID --- .../controller/SubmissionController.java | 30 +++++++++++++++---- .../controller/HttpHeaderBuilder.java | 3 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index bdb9c2352e..1bd965d62f 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -32,6 +32,7 @@ import io.micrometer.core.annotation.Timed; import java.util.ArrayList; import java.util.Collection; +import java.util.UUID; import java.util.stream.IntStream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -123,7 +124,7 @@ public DeferredResult> submitDiagnosisKey( } if (!isEmpty(otp)) { - if (!isSelfReport(exposureKeys)) { + if (!isSelfReport(exposureKeys) || !isUuid(otp)) { return badRequest(); } if (diagnosisKeyService.countTodaysSrs() >= submissionServiceConfig.getMaxSrsPerDay()) { @@ -155,7 +156,7 @@ public DeferredResult> submitDiagnosisKey( * * @see SubmissionType */ - private boolean validSubmissionType(final SubmissionPayload payload) { + public static boolean validSubmissionType(final SubmissionPayload payload) { return 0 <= payload.getSubmissionType().getNumber() && payload.getSubmissionType().getNumber() <= SUBMISSION_TYPE_HOST_WARNING_VALUE; } @@ -169,21 +170,38 @@ private boolean validSubmissionType(final SubmissionPayload payload) { * * @see SubmissionType */ - private boolean isSelfReport(final SubmissionPayload payload) { + public static boolean isSelfReport(final SubmissionPayload payload) { return SUBMISSION_TYPE_SRS_SELF_TEST_VALUE <= payload.getSubmissionType().getNumber() && payload.getSubmissionType().getNumber() <= SUBMISSION_TYPE_SRS_OTHER_VALUE; } + /** + * {@link UUID} syntax check of given string. + * + * @param otp to be checked for valid {@link UUID} syntax. + * @return true if and only if {@link UUID#fromString(String)} doesn't throw + * {@link IllegalArgumentException} for the given otp. + */ + public static boolean isUuid(final String otp) { + try { + UUID.fromString(otp); + return true; + } catch (final IllegalArgumentException e) { + logger.warn(SECURITY, "OTP error ({})", e.getMessage()); + return false; + } + } + /** * {@link ResponseEntity#badRequest()} wrapped into {@link DeferredResult}. * * @return {@link HttpStatus#BAD_REQUEST} */ - private DeferredResult> badRequest() { + public static DeferredResult> badRequest() { return new DeferredResult<>(null, () -> ResponseEntity.badRequest().build()); } - private DeferredResult> tooManyRequests() { + public static DeferredResult> tooManyRequests() { return new DeferredResult<>(null, () -> ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build()); } @@ -216,7 +234,7 @@ public DeferredResult> submissionOnBehalf( */ private DeferredResult> buildRealDeferredResult(final SubmissionPayload payload, final String tan, final TanVerificationService tanVerifier) { - DeferredResult> deferredResult = new DeferredResult<>(); + final DeferredResult> deferredResult = new DeferredResult<>(); StopWatch stopWatch = new StopWatch(); stopWatch.start(); diff --git a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java index 81d392103a..a96ac0c69b 100644 --- a/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java +++ b/services/submission/src/test/java/app/coronawarn/server/services/submission/controller/HttpHeaderBuilder.java @@ -2,6 +2,7 @@ import static org.springframework.http.MediaType.valueOf; +import java.util.UUID; import org.springframework.http.HttpHeaders; public class HttpHeaderBuilder { @@ -27,7 +28,7 @@ public HttpHeaderBuilder cwaAuth() { } public HttpHeaderBuilder cwaOtp() { - headers.set("cwa-otp", "OTP ok"); + headers.set("cwa-otp", UUID.randomUUID().toString()); return this; } From d3cfdce51216da91c6ad0a2059b4697c2ad396b9 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 14:28:07 +0100 Subject: [PATCH 67/83] refactor --- .../controller/RequestExecutor.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 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 4f9ad74d41..db9bbcde0a 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 @@ -1,5 +1,7 @@ package app.coronawarn.server.services.submission.controller; +import static org.springframework.http.HttpMethod.POST; + import app.coronawarn.server.common.protocols.external.exposurenotification.TemporaryExposureKey; import app.coronawarn.server.common.protocols.internal.SubmissionPayload; import java.net.URI; @@ -23,13 +25,7 @@ public class RequestExecutor { private static final URI SUBMISSION_ON_BEHALF_URL = URI.create("/version/v1/submission-on-behalf"); - private final TestRestTemplate testRestTemplate; - - public RequestExecutor(final TestRestTemplate testRestTemplate) { - this.testRestTemplate = testRestTemplate; - } - - private HttpHeaders buildDefaultHeader() { + public static HttpHeaders buildDefaultHeader() { return HttpHeaderBuilder.builder() .contentTypeProtoBuf() .cwaAuth() @@ -37,7 +33,7 @@ private HttpHeaders buildDefaultHeader() { .build(); } - private HttpHeaders buildSrsHeader() { + public static HttpHeaders buildSrsHeader() { return HttpHeaderBuilder.builder() .contentTypeProtoBuf() .cwaOtp() @@ -45,8 +41,14 @@ private HttpHeaders buildSrsHeader() { .build(); } + private final TestRestTemplate rest; + + public RequestExecutor(final TestRestTemplate testRestTemplate) { + rest = testRestTemplate; + } + public ResponseEntity execute(final HttpMethod method, final RequestEntity requestEntity) { - return testRestTemplate.exchange(SUBMISSION_URL, method, requestEntity, Void.class); + return rest.exchange(SUBMISSION_URL, method, requestEntity, Void.class); } public ResponseEntity executePost(final Collection keys) { @@ -65,17 +67,17 @@ public ResponseEntity executePost(final SubmissionPayload body) { return executePost(body, buildDefaultHeader()); } - public ResponseEntity executeSrsPost(final SubmissionPayload body) { - return executePost(body, buildSrsHeader()); + public ResponseEntity executePost(final SubmissionPayload body, final HttpHeaders headers) { + return execute(POST, new RequestEntity<>(body, headers, POST, SUBMISSION_URL)); } - public ResponseEntity executePost(final SubmissionPayload body, final HttpHeaders headers) { - return execute(HttpMethod.POST, new RequestEntity<>(body, headers, HttpMethod.POST, SUBMISSION_URL)); + public ResponseEntity executeSrsPost(final SubmissionPayload body) { + return executePost(body, buildSrsHeader()); } public ResponseEntity executeSubmissionOnBehalf(final HttpMethod method, final RequestEntity requestEntity) { - return testRestTemplate.exchange(SUBMISSION_ON_BEHALF_URL, method, requestEntity, Void.class); + return rest.exchange(SUBMISSION_ON_BEHALF_URL, method, requestEntity, Void.class); } public ResponseEntity executeSubmissionOnBehalf(final SubmissionPayload body) { @@ -83,7 +85,6 @@ public ResponseEntity executeSubmissionOnBehalf(final SubmissionPayload bo } public ResponseEntity executeSubmissionOnBehalf(final SubmissionPayload body, final HttpHeaders headers) { - return executeSubmissionOnBehalf(HttpMethod.POST, - new RequestEntity<>(body, headers, HttpMethod.POST, SUBMISSION_ON_BEHALF_URL)); + return executeSubmissionOnBehalf(POST, new RequestEntity<>(body, headers, POST, SUBMISSION_ON_BEHALF_URL)); } } From f089f8fca8a996af42cfdf7ff6ff9c40a2df7d89 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 15:14:06 +0100 Subject: [PATCH 68/83] MAX_KEYS_PER_DAY --- .../repository/DiagnosisKeyRepository.java | 9 +- .../service/DiagnosisKeyService.java | 9 +- .../DiagnosisKeyRepositoryTest.java | 2 - .../service/DiagnosisKeyServiceTest.java | 370 +++++++++--------- .../config/SubmissionServiceConfig.java | 10 +- .../controller/SubmissionController.java | 4 +- .../src/main/resources/application.yaml | 4 +- .../controller/SubmissionControllerTest.java | 336 ++++++++-------- .../src/test/resources/application.yaml | 2 +- 9 files changed, 366 insertions(+), 380 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 68a4c34849..4153af2692 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 @@ -115,12 +115,13 @@ List findAllWithTrlGreaterThanOrEqual(final @Param("minTrl") int m boolean recordSrs(final @Param("submission_type") String submissionType); /** - * Counts all entries of 'self_report_submissions' for today. + * Counts all entries that have a submission timestamp newer or equal than the specified one. * - * @return The number of submitted self reports for today. + * @param submissionTimestamp The submission timestamp. + * @return The number of keys. */ - @Query("SELECT COUNT(*) FROM self_report_submissions WHERE submission_date = CURRENT_DATE") - int countTodaysSrs(); + @Query("SELECT COUNT(*) FROM diagnosis_key WHERE submission_timestamp >= :threshold") + int countNewerThan(final @Param("threshold") long submissionTimestamp); /** * Counts all entries of 'self_report_submissions' that have a submission date older than the specified one. diff --git a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java index 6aa6050d53..59ae1ce963 100644 --- a/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java +++ b/common/persistence/src/main/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyService.java @@ -1,6 +1,7 @@ package app.coronawarn.server.common.persistence.service; import static app.coronawarn.server.common.persistence.domain.validation.ValidSubmissionTimestampValidator.SECONDS_PER_HOUR; +import static java.time.LocalDate.now; import static java.time.LocalDateTime.ofInstant; import static java.time.ZoneOffset.UTC; import static org.springframework.data.util.StreamUtils.createStreamFromIterator; @@ -12,6 +13,7 @@ import io.micrometer.core.annotation.Timed; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Collection; import org.slf4j.Logger; @@ -74,7 +76,7 @@ public void applyRetentionPolicy(final int daysToRetain) { @Timed @Transactional public void applySrsRetentionPolicy(final int retentionDays) { - final LocalDate retentionDate = LocalDate.now(UTC).minusDays(retentionDays); + final LocalDate retentionDate = now(UTC).minusDays(retentionDays); final int numberOfDeletions = keyRepository.countSrsOlderThan(retentionDate); logger.info("Deleting {} SRS with a submission date older than {} day(s) ago.", numberOfDeletions, retentionDays); keyRepository.deleteSrsOlderThan(retentionDate); @@ -82,8 +84,9 @@ public void applySrsRetentionPolicy(final int retentionDays) { @Timed @Transactional - public int countTodaysSrs() { - return keyRepository.countTodaysSrs(); + public int countTodaysDiagnosisKeys() { + final long midnightEpochSecond = now(UTC).toEpochSecond(LocalTime.MIDNIGHT, UTC); + return keyRepository.countNewerThan(midnightEpochSecond); } /** diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java index 8e97e9c8c8..7c887bf894 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/repository/DiagnosisKeyRepositoryTest.java @@ -65,11 +65,9 @@ void checkKeyData() { @EnumSource(value = SubmissionType.class, names = { "SUBMISSION_TYPE_SRS_.*" }, mode = Mode.MATCH_ANY) void recordSrsTest(final SubmissionType type) { assertTrue(repository.recordSrs(type.name())); - assertEquals(1, repository.countTodaysSrs()); final LocalDate tomorrow = LocalDate.now().plusDays(1); assertEquals(1, repository.countSrsOlderThan(tomorrow)); repository.deleteSrsOlderThan(tomorrow); - assertEquals(0, repository.countTodaysSrs()); assertEquals(0, repository.countSrsOlderThan(tomorrow)); } diff --git a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java index accd230dce..e59c4b74a6 100644 --- a/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java +++ b/common/persistence/src/test/java/app/coronawarn/server/common/persistence/service/DiagnosisKeyServiceTest.java @@ -4,9 +4,12 @@ import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.assertDiagnosisKeysEqual; import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.buildDiagnosisKeyForDateTime; import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.buildDiagnosisKeyForSubmissionTimestamp; +import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey; +import static app.coronawarn.server.common.persistence.service.DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_PCR_TEST; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_RAPID_TEST; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_OTHER; +import static java.time.LocalDate.now; import static java.time.ZoneOffset.UTC; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; @@ -24,7 +27,6 @@ import java.time.OffsetDateTime; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -39,250 +41,238 @@ class DiagnosisKeyServiceTest { public static final int MIN_TRL = 3; - @Autowired - private DiagnosisKeyService diagnosisKeyService; @Autowired - private DiagnosisKeyRepository diagnosisKeyRepository; + private DiagnosisKeyService service; - @AfterEach - public void tearDown() { - diagnosisKeyRepository.deleteAll(); - } + @Autowired + private DiagnosisKeyRepository repo; @Test - void testRetrievalForEmptyDB() { - var actKeys = diagnosisKeyService.getDiagnosisKeys(); - assertDiagnosisKeysEqual(emptyList(), actKeys); + void ignoresRapidTestDiagnosisKeysWhenPcrTestIsPresent() { + final DiagnosisKey pcrKey = generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST); + service.saveDiagnosisKeys(list(pcrKey)); + Collection storedKeys = service.getDiagnosisKeys(); + assertEquals(1, storedKeys.size()); + assertTrue(storedKeys.contains(pcrKey)); + final DiagnosisKey rapidKey = DiagnosisKey.builder() + .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SUBMISSION_TYPE_RAPID_TEST) + .withRollingStartIntervalNumber(pcrKey.getRollingStartIntervalNumber()) + .withTransmissionRiskLevel(pcrKey.getTransmissionRiskLevel()) + .withConsentToFederation(pcrKey.isConsentToFederation()) + .withCountryCode(pcrKey.getOriginCountry()) + .withDaysSinceOnsetOfSymptoms(pcrKey.getDaysSinceOnsetOfSymptoms()) + .withReportType(pcrKey.getReportType()) + .withRollingPeriod(pcrKey.getRollingPeriod()) + .withSubmissionTimestamp(pcrKey.getSubmissionTimestamp()) + .withVisitedCountries(pcrKey.getVisitedCountries()) + .build(); + service.saveDiagnosisKeys(list(rapidKey)); + storedKeys = service.getDiagnosisKeys(); + assertEquals(1, storedKeys.size()); + assertTrue(storedKeys.contains(pcrKey)); + assertFalse(storedKeys.contains(rapidKey)); } @Test - void testSaveAndRetrieve() { - var expKeys = List.of( - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(false, 1, SUBMISSION_TYPE_PCR_TEST), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(false, 1, SUBMISSION_TYPE_RAPID_TEST), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_RAPID_TEST) - ); - - diagnosisKeyService.saveDiagnosisKeys(expKeys); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); - - assertEquals(4, actKeys.size()); - assertDiagnosisKeysEqual(expKeys, actKeys); + void insertsPcrTestDiagnosisKeysWhenRapidTestIsPresent() { + final DiagnosisKey pcrKey = generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST); + final DiagnosisKey rapidKey = DiagnosisKey.builder() + .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SUBMISSION_TYPE_RAPID_TEST) + .withRollingStartIntervalNumber(pcrKey.getRollingStartIntervalNumber()) + .withTransmissionRiskLevel(pcrKey.getTransmissionRiskLevel()) + .withConsentToFederation(pcrKey.isConsentToFederation()) + .withCountryCode(pcrKey.getOriginCountry()) + .withDaysSinceOnsetOfSymptoms(pcrKey.getDaysSinceOnsetOfSymptoms()) + .withReportType(pcrKey.getReportType()) + .withRollingPeriod(pcrKey.getRollingPeriod()) + .withSubmissionTimestamp(pcrKey.getSubmissionTimestamp()) + .withVisitedCountries(pcrKey.getVisitedCountries()) + .build(); + service.saveDiagnosisKeys(list(rapidKey)); + Collection storedKeys = service.getDiagnosisKeys(); + assertEquals(1, storedKeys.size()); + assertTrue(storedKeys.contains(rapidKey)); + service.saveDiagnosisKeys(list(pcrKey)); + storedKeys = service.getDiagnosisKeys(); + assertEquals(2, storedKeys.size()); + assertTrue(storedKeys.contains(rapidKey)); + assertTrue(storedKeys.contains(pcrKey)); } - @Test - void testSaveAndRetrieveKeysFilteredByTrl() { - var filterOutKeysBasedOnTrl = List.of( - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(false, daysToSeconds(1), - SUBMISSION_TYPE_PCR_TEST, 1), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(false, daysToSeconds(1), - SUBMISSION_TYPE_RAPID_TEST, 2) - ); - - var expKeys = List.of( - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(1), - SUBMISSION_TYPE_PCR_TEST, 3), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(1), - SUBMISSION_TYPE_RAPID_TEST, 4) - ); - - var oldKeys = List.of( - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(42), - SUBMISSION_TYPE_PCR_TEST, 3), - DiagnosisKeyServiceTestHelper.generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(42), - SUBMISSION_TYPE_RAPID_TEST, 4) - ); - - diagnosisKeyService.saveDiagnosisKeys(filterOutKeysBasedOnTrl); - diagnosisKeyService.saveDiagnosisKeys(oldKeys); - diagnosisKeyService.saveDiagnosisKeys(expKeys); - - var actKeys = diagnosisKeyService.getDiagnosisKeysWithMinTrl(MIN_TRL, 10); - - assertEquals(2, actKeys.size()); - assertDiagnosisKeysEqual(expKeys, actKeys); + @ParameterizedTest + @EnumSource(value = SubmissionType.class) + void recordSrsTest(final SubmissionType type) { + service.recordSrs(type); } @Test - void testSortedRetrievalResult() { - var expKeys = list( - buildDiagnosisKeyForSubmissionTimestamp(2L), - buildDiagnosisKeyForSubmissionTimestamp(1L)); + void shouldDoesntExist() { + assertFalse(service.exists(null)); + assertFalse(service.exists(Collections.emptyList())); + } - diagnosisKeyService.saveDiagnosisKeys(expKeys); + @ParameterizedTest + @ValueSource(ints = { 0, 1 }) + void shouldNotUpdateExistingKeyWithSameSubmissionType(final int submissionTypeNumber) { + final var keyData = "1234567890123456"; + final var keys = list(DiagnosisKey.builder() + .withKeyDataAndSubmissionType(keyData.getBytes(), SubmissionType.forNumber(submissionTypeNumber)) + .withRollingStartIntervalNumber(600) + .withTransmissionRiskLevel(2) + .withCountryCode("DE") + .withVisitedCountries(Set.of("DE")) + .withSubmissionTimestamp(0L) + .withReportType(ReportType.CONFIRMED_TEST) + .build(), + DiagnosisKey.builder() + .withKeyDataAndSubmissionType(keyData.getBytes(), SubmissionType.forNumber(submissionTypeNumber)) + .withRollingStartIntervalNumber(600) + .withTransmissionRiskLevel(3) + .withCountryCode("DE") + .withVisitedCountries(Set.of("DE")) + .withSubmissionTimestamp(0L) + .withReportType(ReportType.CONFIRMED_TEST) + .build()); - // reverse to match expected sort order - Collections.reverse(expKeys); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); + final int actNumberOfInsertedRows = service.saveDiagnosisKeys(keys); + final var actKeys = service.getDiagnosisKeys(); - assertDiagnosisKeysEqual(expKeys, actKeys); + assertThat(actNumberOfInsertedRows).isEqualTo(1); + assertThat(actKeys).hasSize(1); + assertThat(actKeys.iterator().next().getTransmissionRiskLevel()).isEqualTo(2); } - @DisplayName("Assert a positive retention period is accepted.") - @ValueSource(ints = {0, 1, Integer.MAX_VALUE}) - @ParameterizedTest - void testApplyRetentionPolicyForValidNumberOfDays(int daysToRetain) { - assertThatCode(() -> diagnosisKeyService.applyRetentionPolicy(daysToRetain)) - .doesNotThrowAnyException(); + /** + * record 3 self reports for today, remove all older than yesterday, check that the ones from today are still there, + * delete also from today, finally check that there are 0 for today. + */ + @Test + void srsTests() { + service.recordSrs(SUBMISSION_TYPE_SRS_OTHER); + service.recordSrs(SUBMISSION_TYPE_SRS_OTHER); + service.recordSrs(SUBMISSION_TYPE_SRS_OTHER); + assertEquals(3, repo.countSrsOlderThan(now(UTC).plusDays(1))); + service.applySrsRetentionPolicy(1); + assertEquals(3, repo.countSrsOlderThan(now(UTC).plusDays(1))); + service.applySrsRetentionPolicy(0); + assertEquals(0, repo.countSrsOlderThan(now(UTC).plusDays(1))); } - @DisplayName("Assert a negative retention period is rejected.") - @ValueSource(ints = {Integer.MIN_VALUE, -1}) - @ParameterizedTest - void testApplyRetentionPolicyForNegativeNumberOfDays(int daysToRetain) { - assertThat(catchThrowable(() -> diagnosisKeyService.applyRetentionPolicy(daysToRetain))) - .isInstanceOf(IllegalArgumentException.class); + @AfterEach + public void tearDown() { + repo.deleteAll(); } @Test void testApplyRetentionPolicyForEmptyDb() { - diagnosisKeyService.applyRetentionPolicy(1); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); + service.applyRetentionPolicy(1); + final var actKeys = service.getDiagnosisKeys(); assertThat(actKeys).isEmpty(); } + @DisplayName("Assert a negative retention period is rejected.") + @ValueSource(ints = { Integer.MIN_VALUE, -1 }) + @ParameterizedTest + void testApplyRetentionPolicyForNegativeNumberOfDays(final int daysToRetain) { + assertThat(catchThrowable(() -> service.applyRetentionPolicy(daysToRetain))) + .isInstanceOf(IllegalArgumentException.class); + } + @Test - void testApplyRetentionPolicyForOneNotApplicableEntry() { - var expKeys = list(buildDiagnosisKeyForDateTime(OffsetDateTime.now(UTC).minusDays(1L))); + void testApplyRetentionPolicyForOneApplicableEntry() { + final var keys = list(buildDiagnosisKeyForDateTime(OffsetDateTime.now(UTC).minusDays(1L).minusHours(1))); - diagnosisKeyService.saveDiagnosisKeys(expKeys); - diagnosisKeyService.applyRetentionPolicy(1); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); + service.saveDiagnosisKeys(keys); + service.applyRetentionPolicy(1); + final var actKeys = service.getDiagnosisKeys(); - assertDiagnosisKeysEqual(expKeys, actKeys); + assertThat(actKeys).isEmpty(); } @Test - void testApplyRetentionPolicyForOneApplicableEntry() { - var keys = list(buildDiagnosisKeyForDateTime(OffsetDateTime.now(UTC).minusDays(1L).minusHours(1))); + void testApplyRetentionPolicyForOneNotApplicableEntry() { + final var expKeys = list(buildDiagnosisKeyForDateTime(OffsetDateTime.now(UTC).minusDays(1L))); - diagnosisKeyService.saveDiagnosisKeys(keys); - diagnosisKeyService.applyRetentionPolicy(1); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); + service.saveDiagnosisKeys(expKeys); + service.applyRetentionPolicy(1); + final var actKeys = service.getDiagnosisKeys(); - assertThat(actKeys).isEmpty(); + assertDiagnosisKeysEqual(expKeys, actKeys); } + @DisplayName("Assert a positive retention period is accepted.") + @ValueSource(ints = { 0, 1, Integer.MAX_VALUE }) @ParameterizedTest - @ValueSource(ints = {0, 1}) - void shouldNotUpdateExistingKeyWithSameSubmissionType(int submissionTypeNumber) { - var keyData = "1234567890123456"; - var keys = list(DiagnosisKey.builder() - .withKeyDataAndSubmissionType(keyData.getBytes(), SubmissionType.forNumber(submissionTypeNumber)) - .withRollingStartIntervalNumber(600) - .withTransmissionRiskLevel(2) - .withCountryCode("DE") - .withVisitedCountries(Set.of("DE")) - .withSubmissionTimestamp(0L) - .withReportType(ReportType.CONFIRMED_TEST) - .build(), - DiagnosisKey.builder() - .withKeyDataAndSubmissionType(keyData.getBytes(), SubmissionType.forNumber(submissionTypeNumber)) - .withRollingStartIntervalNumber(600) - .withTransmissionRiskLevel(3) - .withCountryCode("DE") - .withVisitedCountries(Set.of("DE")) - .withSubmissionTimestamp(0L) - .withReportType(ReportType.CONFIRMED_TEST) - .build()); - - int actNumberOfInsertedRows = diagnosisKeyService.saveDiagnosisKeys(keys); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); + void testApplyRetentionPolicyForValidNumberOfDays(final int daysToRetain) { + assertThatCode(() -> service.applyRetentionPolicy(daysToRetain)).doesNotThrowAnyException(); + } - assertThat(actNumberOfInsertedRows).isEqualTo(1); - assertThat(actKeys).hasSize(1); - assertThat(actKeys.iterator().next().getTransmissionRiskLevel()).isEqualTo(2); + @Test + void testRetrievalForEmptyDB() { + final var actKeys = service.getDiagnosisKeys(); + assertDiagnosisKeysEqual(emptyList(), actKeys); } @Test void testReturnedNumberOfInsertedKeysForNoConflict() { - var keys = list( - buildDiagnosisKeyForSubmissionTimestamp(1L), - buildDiagnosisKeyForSubmissionTimestamp(2L)); + final var keys = list(buildDiagnosisKeyForSubmissionTimestamp(1L), buildDiagnosisKeyForSubmissionTimestamp(2L)); - diagnosisKeyService.saveDiagnosisKeys(keys); + service.saveDiagnosisKeys(keys); - var actKeys = diagnosisKeyService.getDiagnosisKeys(); + final var actKeys = service.getDiagnosisKeys(); assertThat(actKeys).hasSize(2); } @Test - void insertsPcrTestDiagnosisKeysWhenRapidTestIsPresent() { - DiagnosisKey pcrKey = DiagnosisKeyServiceTestHelper - .generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST); - DiagnosisKey rapidKey = DiagnosisKey.builder() - .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SUBMISSION_TYPE_RAPID_TEST) - .withRollingStartIntervalNumber(pcrKey.getRollingStartIntervalNumber()) - .withTransmissionRiskLevel(pcrKey.getTransmissionRiskLevel()) - .withConsentToFederation(pcrKey.isConsentToFederation()) - .withCountryCode(pcrKey.getOriginCountry()) - .withDaysSinceOnsetOfSymptoms(pcrKey.getDaysSinceOnsetOfSymptoms()) - .withReportType(pcrKey.getReportType()) - .withRollingPeriod(pcrKey.getRollingPeriod()) - .withSubmissionTimestamp(pcrKey.getSubmissionTimestamp()) - .withVisitedCountries(pcrKey.getVisitedCountries()) - .build(); - Collection storedKeys; - diagnosisKeyService.saveDiagnosisKeys(List.of(rapidKey)); - storedKeys = diagnosisKeyService.getDiagnosisKeys(); - assertEquals(1, storedKeys.size()); - assertTrue(storedKeys.contains(rapidKey)); - diagnosisKeyService.saveDiagnosisKeys(List.of(pcrKey)); - storedKeys = diagnosisKeyService.getDiagnosisKeys(); - assertEquals(2, storedKeys.size()); - assertTrue(storedKeys.contains(rapidKey)); - assertTrue(storedKeys.contains(pcrKey)); + void testSaveAndRetrieve() { + final var expKeys = list( + generateRandomDiagnosisKey(false, 1, SUBMISSION_TYPE_PCR_TEST), + generateRandomDiagnosisKey(false, 1, SUBMISSION_TYPE_RAPID_TEST), + generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST), + generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_RAPID_TEST)); + + service.saveDiagnosisKeys(expKeys); + final var actKeys = service.getDiagnosisKeys(); + + assertEquals(4, actKeys.size()); + assertDiagnosisKeysEqual(expKeys, actKeys); } @Test - void ignoresRapidTestDiagnosisKeysWhenPcrTestIsPresent() { - DiagnosisKey pcrKey = DiagnosisKeyServiceTestHelper - .generateRandomDiagnosisKey(true, 1, SUBMISSION_TYPE_PCR_TEST); - Collection storedKeys; - diagnosisKeyService.saveDiagnosisKeys(List.of(pcrKey)); - storedKeys = diagnosisKeyService.getDiagnosisKeys(); - assertEquals(1, storedKeys.size()); - assertTrue(storedKeys.contains(pcrKey)); - final DiagnosisKey rapidKey = DiagnosisKey.builder() - .withKeyDataAndSubmissionType(pcrKey.getKeyData(), SUBMISSION_TYPE_RAPID_TEST) - .withRollingStartIntervalNumber(pcrKey.getRollingStartIntervalNumber()) - .withTransmissionRiskLevel(pcrKey.getTransmissionRiskLevel()) - .withConsentToFederation(pcrKey.isConsentToFederation()) - .withCountryCode(pcrKey.getOriginCountry()) - .withDaysSinceOnsetOfSymptoms(pcrKey.getDaysSinceOnsetOfSymptoms()) - .withReportType(pcrKey.getReportType()) - .withRollingPeriod(pcrKey.getRollingPeriod()) - .withSubmissionTimestamp(pcrKey.getSubmissionTimestamp()) - .withVisitedCountries(pcrKey.getVisitedCountries()) - .build(); - diagnosisKeyService.saveDiagnosisKeys(List.of(rapidKey)); - storedKeys = diagnosisKeyService.getDiagnosisKeys(); - assertEquals(1, storedKeys.size()); - assertTrue(storedKeys.contains(pcrKey)); - assertFalse(storedKeys.contains(rapidKey)); - } + void testSaveAndRetrieveKeysFilteredByTrl() { + final var filterOutKeysBasedOnTrl = list( + generateRandomDiagnosisKeyWithSpecifiedTrl(false, daysToSeconds(1), SUBMISSION_TYPE_PCR_TEST, 1), + generateRandomDiagnosisKeyWithSpecifiedTrl(false, daysToSeconds(1), SUBMISSION_TYPE_RAPID_TEST, 2)); - @ParameterizedTest - @EnumSource(value = SubmissionType.class) - void recordSrsTest(final SubmissionType type) { - diagnosisKeyService.recordSrs(type); + final var expKeys = list( + generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(1), SUBMISSION_TYPE_PCR_TEST, 3), + generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(1), SUBMISSION_TYPE_RAPID_TEST, 4)); + + final var oldKeys = list( + generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(42), SUBMISSION_TYPE_PCR_TEST, 3), + generateRandomDiagnosisKeyWithSpecifiedTrl(true, daysToSeconds(42), SUBMISSION_TYPE_RAPID_TEST, 4)); + + service.saveDiagnosisKeys(filterOutKeysBasedOnTrl); + service.saveDiagnosisKeys(oldKeys); + service.saveDiagnosisKeys(expKeys); + + final var actKeys = service.getDiagnosisKeysWithMinTrl(MIN_TRL, 10); + + assertEquals(2, actKeys.size()); + assertDiagnosisKeysEqual(expKeys, actKeys); } - /** - * record 3 self reports for today, remove all older than yesterday, check that the ones from today are still there, - * delete also from today, finally check that there are 0 for today. - */ @Test - void srsTests() { - diagnosisKeyService.recordSrs(SUBMISSION_TYPE_SRS_OTHER); - diagnosisKeyService.recordSrs(SUBMISSION_TYPE_SRS_OTHER); - diagnosisKeyService.recordSrs(SUBMISSION_TYPE_SRS_OTHER); - assertEquals(3, diagnosisKeyService.countTodaysSrs()); - diagnosisKeyService.applySrsRetentionPolicy(1); - assertEquals(3, diagnosisKeyService.countTodaysSrs()); - diagnosisKeyService.applySrsRetentionPolicy(0); - assertEquals(0, diagnosisKeyService.countTodaysSrs()); + void testSortedRetrievalResult() { + final var expKeys = list(buildDiagnosisKeyForSubmissionTimestamp(2L), buildDiagnosisKeyForSubmissionTimestamp(1L)); + + service.saveDiagnosisKeys(expKeys); + + // reverse to match expected sort order + Collections.reverse(expKeys); + final var actKeys = service.getDiagnosisKeys(); + + assertDiagnosisKeysEqual(expKeys, actKeys); } } 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 8fb9df08cc..a24c1c4c91 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 @@ -66,7 +66,7 @@ public class SubmissionServiceConfig { private Integer minRollingPeriod; @Min(1) @Max(100000000) - private int maxSrsPerDay; + private int maxKeysPerDay; /** * unencryptedCheckinsEnabled. @@ -241,12 +241,12 @@ public void setUnencryptedCheckinsEnabled(Boolean unencryptedCheckinsEnabled) { this.unencryptedCheckinsEnabled = unencryptedCheckinsEnabled; } - public int getMaxSrsPerDay() { - return maxSrsPerDay; + public int getMaxKeysPerDay() { + return maxKeysPerDay; } - public void setMaxSrsPerDay(final int maxSrsPerDay) { - this.maxSrsPerDay = maxSrsPerDay; + public void setMaxKeysPerDay(final int maxKeysPerDay) { + this.maxKeysPerDay = maxKeysPerDay; } public static class Payload { diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 1bd965d62f..496ee374ab 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -127,9 +127,9 @@ public DeferredResult> submitDiagnosisKey( if (!isSelfReport(exposureKeys) || !isUuid(otp)) { return badRequest(); } - if (diagnosisKeyService.countTodaysSrs() >= submissionServiceConfig.getMaxSrsPerDay()) { + if (diagnosisKeyService.countTodaysDiagnosisKeys() >= submissionServiceConfig.getMaxKeysPerDay()) { logger.warn("We reached the maximum number ({}) of allowed Self-Report-Submissions for today ({})!", - submissionServiceConfig.getMaxSrsPerDay(), now(UTC)); + submissionServiceConfig.getMaxKeysPerDay(), now(UTC)); return tooManyRequests(); } submissionMonitor.incrementRequestCounter(); diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 5a176b1428..921b9e2ed0 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -45,8 +45,8 @@ services: min-rolling-period: ${MIN_ROLLING_PERIOD:1} # The flag for unencrypted checkins used by the EventCheckinFacade. unencrypted-checkins-enabled: ${UNENCRYPTED_CHECKINS_ENABLED:false} - # To prevent a potential overload of the distribution and the clients, we'll allow only this amount of self reports per day - max-srs-per-day: ${MAX_SRS_PER_DAY:1000000} + # To prevent a potential overload of the distribution and the clients, we'll allow only this amount of keys per day + max-keys-per-day: ${MAX_KEYS_PER_DAY:1000000} payload: # The maximum number of keys accepted for any submission. max-number-of-keys: ${MAX_NUMBER_OF_KEYS:14} 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 6294519fa5..49348d786e 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 @@ -2,6 +2,7 @@ import static app.coronawarn.server.common.persistence.service.utils.checkins.CheckinsDateSpecification.TEN_MINUTE_INTERVAL_DERIVATION; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_SELF_TEST; +import static app.coronawarn.server.services.submission.controller.RequestExecutor.buildSrsHeader; import static app.coronawarn.server.services.submission.controller.SubmissionController.CWA_KEYS_TRUNCATED_HEADER; 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; @@ -83,7 +84,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity.BodyBuilder; import org.springframework.test.annotation.DirtiesContext; @@ -93,6 +93,34 @@ @SuppressWarnings({ "unchecked", "deprecation" }) class SubmissionControllerTest { + public static SubmissionPayload buildPayloadWithInvalidKey() { + final TemporaryExposureKey invalidKey = buildTemporaryExposureKey(VALID_KEY_DATA_1, + createRollingStartIntervalNumber(2), 999, ReportType.CONFIRMED_TEST, 1); + 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())); + } + + private static Stream invalidVisitedCountries() { + return Stream.of(Arguments.of(List.of("")), Arguments.of(List.of("D")), Arguments.of(List.of("FRE")), + Arguments.of(List.of("DE", "XX")), Arguments.of(List.of("DE", "FRE"))); + } + + private static Stream validVisitedCountries() { + return Stream.of(Arguments.of(List.of("DE")), Arguments.of(List.of("DE", "FR"))); + } + @MockBean private DiagnosisKeyService diagnosisKeyService; @@ -117,14 +145,6 @@ class SubmissionControllerTest { @Autowired private TraceTimeIntervalWarningRepository traceTimeIntervalWarningRepository; - @BeforeEach - public void setUpMocks() { - traceTimeIntervalWarningRepository.deleteAll(); - when(tanVerifier.verifyTan(anyString())).thenReturn(true); - when(srsOtpVerifier.verifyTan(anyString())).thenReturn(true); - when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); - } - private void assertDSOSCorrectlyComputedFromTRL(final SubmissionServiceConfig config, final Collection submittedTEKs, final Collection diagnosisKeys) { submittedTEKs.stream().map(tek -> Pair.of(tek, findDiagnosisKeyMatch(tek, diagnosisKeys))).forEach(pair -> { @@ -176,7 +196,7 @@ private void assertSubmissionPayloadKeysCorrespondToEachOther( private void assertTraceWarningsHaveBeenSaved(final int numberOfExpectedWarningsSaved) { final List storedTimeIntervalWarnings = StreamSupport - .stream(traceTimeIntervalWarningRepository.findAll().spliterator(), false).collect(Collectors.toList()); + .stream(traceTimeIntervalWarningRepository.findAll().spliterator(), false).toList(); assertEquals(numberOfExpectedWarningsSaved, storedTimeIntervalWarnings.size()); } @@ -191,33 +211,19 @@ private void assertTRLCorrectlyComputedFromDSOS(final SubmissionServiceConfig co }); } - private TemporaryExposureKey createOutdatedKey() { - return TemporaryExposureKey.newBuilder().setKeyData(ByteString.copyFromUtf8(VALID_KEY_DATA_2)) - .setRollingStartIntervalNumber(createRollingStartIntervalNumber(config.getRetentionDays() + 1)) - .setRollingPeriod(DiagnosisKey.MAX_ROLLING_PERIOD).setTransmissionRiskLevel(5).build(); - } - - private DiagnosisKey findDiagnosisKeyMatch(final TemporaryExposureKey temporaryExposureKey, - final Collection diagnosisKeys) { - return diagnosisKeys.stream() - .filter( - diagnosisKey -> temporaryExposureKey.getKeyData().equals(ByteString.copyFrom(diagnosisKey.getKeyData()))) - .findFirst().orElseThrow(); - } - @ParameterizedTest @MethodSource("createIncompleteHeaders") void badRequestIfCwaHeadersMissing(final HttpHeaders headers) { - final ResponseEntity actResponse = executor.executePost(buildPayloadWithOneKey(), headers); + final var response = executor.executePost(buildPayloadWithOneKey(), headers); verify(diagnosisKeyService, never()).saveDiagnosisKeys(any()); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } @Test void check400ResponseStatusForInvalidKeys() { - final ResponseEntity actResponse = executor.executePost(buildPayloadWithInvalidKey()); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + final var response = executor.executePost(buildPayloadWithInvalidKey()); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } /** @@ -231,7 +237,6 @@ void check400ResponseStatusForInvalidKeys() { void checkDSOSIsPersistedForKeysWithTRLOnly() { final Collection submittedKeys = buildMultipleKeysWithoutDSOS(config); final ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); - final SubmissionPayload submissionPayload = buildPayload(submittedKeys); executor.executePost(submissionPayload); @@ -251,7 +256,7 @@ void checkDSOSIsPersistedForKeysWithTRLOnly() { void checkErrorIsThrownWhenKeysAreMissingDSOSAndTRL() { final Collection submittedKeys = buildMultipleKeysWithoutDSOSAndTRL(config); final SubmissionPayload submissionPayload = buildPayload(submittedKeys); - final ResponseEntity response = executor.executePost(submissionPayload); + final var response = executor.executePost(submissionPayload); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } @@ -293,14 +298,14 @@ void checkRealRequestHandlingIsMonitored() { @Test void checkResponseStatusForValidParameters() { - final ResponseEntity actResponse = executor.executePost(buildPayload(buildMultipleKeys(config))); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayload(buildMultipleKeys(config))); + assertThat(response.getStatusCode()).isEqualTo(OK); } @Test void checkResponseStatusForValidParametersWithPadding() { - final ResponseEntity actResponse = executor.executePost(buildPayloadWithPadding(buildMultipleKeys(config))); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayloadWithPadding(buildMultipleKeys(config))); + assertThat(response.getStatusCode()).isEqualTo(OK); } @Test @@ -337,15 +342,29 @@ void checkTRLIsPersistedForKeysWithDSOSOnly() { assertTRLCorrectlyComputedFromDSOS(config, submittedKeys, values); } + private TemporaryExposureKey createOutdatedKey() { + return TemporaryExposureKey.newBuilder().setKeyData(ByteString.copyFromUtf8(VALID_KEY_DATA_2)) + .setRollingStartIntervalNumber(createRollingStartIntervalNumber(config.getRetentionDays() + 1)) + .setRollingPeriod(DiagnosisKey.MAX_ROLLING_PERIOD).setTransmissionRiskLevel(5).build(); + } + + private DiagnosisKey findDiagnosisKeyMatch(final TemporaryExposureKey temporaryExposureKey, + final Collection diagnosisKeys) { + return diagnosisKeys.stream() + .filter( + diagnosisKey -> temporaryExposureKey.getKeyData().equals(ByteString.copyFrom(diagnosisKey.getKeyData()))) + .findFirst().orElseThrow(); + } + @Test void invalidTanHandling() { when(tanVerifier.verifyTan(anyString())).thenReturn(false); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithOneKey()); + final var response = executor.executePost(buildPayloadWithOneKey()); verify(diagnosisKeyService, never()).saveDiagnosisKeys(any()); verify(fakeDelayManager, times(1)).updateFakeRequestDelay(anyLong()); - assertThat(actResponse.getStatusCode()).isEqualTo(FORBIDDEN); + assertThat(response.getStatusCode()).isEqualTo(FORBIDDEN); } @Test @@ -363,6 +382,14 @@ void keysWithOutdatedRollingStartIntervalNumberDoNotGetSaved() { assertSubmissionPayloadKeysCorrespondToEachOther(submittedKeys, argument.getValue(), submissionPayload); } + @BeforeEach + public void setUpMocks() { + traceTimeIntervalWarningRepository.deleteAll(); + when(tanVerifier.verifyTan(anyString())).thenReturn(true); + when(srsOtpVerifier.verifyTan(anyString())).thenReturn(true); + when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); + } + @Test void singleKeyWithOutdatedRollingStartIntervalNumberDoesNotGetSaved() { final ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); @@ -415,9 +442,9 @@ void submissionPayloadWithoutConsentIsPersistedCorrectly() { @Test void submmissionPayloadWithInvalidIntervalItemsIsFilteredCorrectly() { - int daysIntoTheFuture = 5; + final int daysIntoTheFuture = 5; final Collection submittedKeys = buildMultipleKeys(config); - TemporaryExposureKey futureKey = buildKeyWithFutureInterval(daysIntoTheFuture); + final TemporaryExposureKey futureKey = buildKeyWithFutureInterval(daysIntoTheFuture); submittedKeys.add(futureKey); final ArgumentCaptor> argument = ArgumentCaptor.forClass(Collection.class); @@ -426,26 +453,49 @@ void submmissionPayloadWithInvalidIntervalItemsIsFilteredCorrectly() { executor.executePost(submissionPayload); verify(diagnosisKeyService, atLeastOnce()).saveDiagnosisKeys(argument.capture()); - Collection savedDiagnosisKeys = argument.getValue(); - int expectedNumberofSavedKeys = (submittedKeys.size() - 1) * config.getRandomKeyPaddingMultiplier(); + final Collection savedDiagnosisKeys = argument.getValue(); + final int expectedNumberofSavedKeys = (submittedKeys.size() - 1) * config.getRandomKeyPaddingMultiplier(); assertThat(savedDiagnosisKeys).hasSize(expectedNumberofSavedKeys); - assertThat(savedDiagnosisKeys.stream().anyMatch(diagnosisKey -> diagnosisKey.getRollingStartIntervalNumber() == futureKey.getRollingStartIntervalNumber()) == false); + assertThat(!savedDiagnosisKeys.stream().anyMatch(diagnosisKey -> diagnosisKey + .getRollingStartIntervalNumber() == futureKey.getRollingStartIntervalNumber())); + } + + @Test + void testCheckinDataHeadersAreCorrectlyFilled() { + final Integer daysInThePast = config.getAcceptedEventDateThresholdDays() + 1; + final Instant thisInstant = Instant.now(); + final long eventCheckoutInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast).toEpochSecond(UTC); + final long eventCheckinInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1).toEpochSecond(UTC); + final long eventCheckinInAllowedPeriod = ofInstant(Instant.now(), UTC).minusDays(10).toEpochSecond(UTC); + + final List checkins = List + .of(CheckIn.newBuilder().setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInThePast)) + .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast)) + .setTransmissionRiskLevel(1).setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build(), + CheckIn.newBuilder().setTransmissionRiskLevel(3) + .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInAllowedPeriod)) + .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInAllowedPeriod) + 10) + .setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build()); + + final var response = executor.executePost(buildPayloadWithCheckinData(checkins)); + assertThat(response.getStatusCode()).isEqualTo(OK); + assertThat(response.getHeaders().get("cwa-filtered-checkins")).contains("1", Index.atIndex(0)); + assertThat(response.getHeaders().get("cwa-saved-checkins")).contains("2", Index.atIndex(0)); } @Test void testCheckinDataIsFilteredForFutureEvents() { final Instant thisInstant = Instant.now(); final long eventCheckinInTheFuture = ofInstant(thisInstant, UTC).plusMinutes(11).toEpochSecond(UTC); - final long eventCheckoutInTheFuture = ofInstant(thisInstant, UTC).plusMinutes(20) - .toEpochSecond(UTC); + final long eventCheckoutInTheFuture = ofInstant(thisInstant, UTC).plusMinutes(20).toEpochSecond(UTC); final List checkins = List .of(CheckIn.newBuilder().setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheFuture)) .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInTheFuture)) .setTransmissionRiskLevel(3).setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build()); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(checkins)); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayloadWithCheckinData(checkins)); + assertThat(response.getStatusCode()).isEqualTo(OK); assertTraceWarningsHaveBeenSaved(0); } @@ -453,48 +503,19 @@ void testCheckinDataIsFilteredForFutureEvents() { void testCheckinDataIsFilteredForOldEvents() { final Integer daysInThePast = config.getAcceptedEventDateThresholdDays() + 1; final Instant thisInstant = Instant.now(); - final long eventCheckoutInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast) - .toEpochSecond(UTC); - final long eventCheckinInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1) - .toEpochSecond(UTC); + final long eventCheckoutInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast).toEpochSecond(UTC); + final long eventCheckinInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1).toEpochSecond(UTC); final List checkins = List .of(CheckIn.newBuilder().setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInThePast)) .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast)) .setTransmissionRiskLevel(1).setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build()); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(checkins)); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayloadWithCheckinData(checkins)); + assertThat(response.getStatusCode()).isEqualTo(OK); assertTraceWarningsHaveBeenSaved(0); } - @Test - void testCheckinDataHeadersAreCorrectlyFilled() { - final Integer daysInThePast = config.getAcceptedEventDateThresholdDays() + 1; - final Instant thisInstant = Instant.now(); - final long eventCheckoutInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast) - .toEpochSecond(UTC); - final long eventCheckinInThePast = ofInstant(thisInstant, UTC).minusDays(daysInThePast + 1) - .toEpochSecond(UTC); - - final long eventCheckinInAllowedPeriod = ofInstant(Instant.now(), UTC).minusDays(10) - .toEpochSecond(UTC); - - final List checkins = List - .of(CheckIn.newBuilder().setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInThePast)) - .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast)) - .setTransmissionRiskLevel(1).setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build(), - CheckIn.newBuilder().setTransmissionRiskLevel(3) - .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInAllowedPeriod)) - .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInAllowedPeriod) + 10) - .setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build()); - - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(checkins)); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); - assertThat(actResponse.getHeaders().get("cwa-filtered-checkins")).contains("1", Index.atIndex(0)); - assertThat(actResponse.getHeaders().get("cwa-saved-checkins")).contains("2", Index.atIndex(0)); - } - @Test void testCheckinDataIsFilteredForTransmissionRiskLevel() { final long eventCheckinInThePast = ofInstant(Instant.now(), UTC).minusDays(10).toEpochSecond(UTC); @@ -510,16 +531,15 @@ void testCheckinDataIsFilteredForTransmissionRiskLevel() { .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInThePast) + 22) .setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build()); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(invalidCheckinData)); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayloadWithCheckinData(invalidCheckinData)); + assertThat(response.getStatusCode()).isEqualTo(OK); assertTraceWarningsHaveBeenSaved(0); } @Test void testEmptyOriginCountrySubmissionPayload() { - final ResponseEntity actResponse = executor - .executePost(buildPayloadForOriginCountry(buildMultipleKeys(config), "")); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayloadForOriginCountry(buildMultipleKeys(config), "")); + assertThat(response.getStatusCode()).isEqualTo(OK); } @Test @@ -527,23 +547,21 @@ void testInvalidCheckOutTime() { final List invalidCheckinData = List.of( CheckIn.newBuilder().setTransmissionRiskLevel(2).setStartIntervalNumber(4).setEndIntervalNumber(3).build()); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(invalidCheckinData)); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + final var response = executor.executePost(buildPayloadWithCheckinData(invalidCheckinData)); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); assertTraceWarningsHaveBeenSaved(0); } @Test void testInvalidOriginCountrySubmissionPayload() { - final ResponseEntity actResponse = executor - .executePost(buildPayloadForOriginCountry(buildMultipleKeys(config), "IT")); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + final var response = executor.executePost(buildPayloadForOriginCountry(buildMultipleKeys(config), "IT")); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } @Test void testInvalidPaddingSubmissionPayload() { - final ResponseEntity actResponse = executor - .executePost(buildPayloadWithTooLargePadding(config, buildMultipleKeys(config))); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + final var response = executor.executePost(buildPayloadWithTooLargePadding(config, buildMultipleKeys(config))); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } @Test @@ -552,28 +570,71 @@ void testInvalidTransmissionRiskLevelInCheckinData() { CheckIn.newBuilder().setTransmissionRiskLevel(0).setStartIntervalNumber(1).setEndIntervalNumber(2).build(), CheckIn.newBuilder().setTransmissionRiskLevel(4).setStartIntervalNumber(1).setEndIntervalNumber(1).build()); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(invalidCheckinData)); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + final var response = executor.executePost(buildPayloadWithCheckinData(invalidCheckinData)); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); assertTraceWarningsHaveBeenSaved(0); } @ParameterizedTest @MethodSource("invalidVisitedCountries") void testInvalidVisitedCountriesSubmissionPayload(final List visitedCountries) { - final ResponseEntity actResponse = executor.executePost(buildPayloadWithVisitedCountries(visitedCountries)); - assertThat(actResponse.getStatusCode()).isEqualTo(BAD_REQUEST); + final var response = executor.executePost(buildPayloadWithVisitedCountries(visitedCountries)); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } @Test void testMissingOriginCountrySubmissionPayload() { - final ResponseEntity actResponse = executor - .executePost(buildPayloadWithoutOriginCountry(buildMultipleKeys(config))); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); + final var response = executor.executePost(buildPayloadWithoutOriginCountry(buildMultipleKeys(config))); + assertThat(response.getStatusCode()).isEqualTo(OK); } + /** + * @see SubmissionController#extractAndStoreDiagnosisKeys(SubmissionPayload, BodyBuilder) + */ @Test - void testValidCheckinData() { + void testSrsKeysSendTwice() { + // fake existing keys + when(diagnosisKeyService.exists(any())).thenReturn(true); + // send again + final var response = executor.executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); + } + /** + * @see SubmissionController#extractValidDiagnosisKeysFromPayload(SubmissionPayload, BodyBuilder) + */ + @Test + void testSrsKeysTruncated() { + final var response = executor.executeSrsPost( + // creates 3 keys around 14 days, which will be filtered and 3 more around the 'srsDays' + buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(OK); + assertThat(response.getHeaders()).containsKey(CWA_KEYS_TRUNCATED_HEADER); + assertThat(response.getHeaders()).containsEntry(CWA_KEYS_TRUNCATED_HEADER, List.of(valueOf(config.getSrsDays()))); + } + + /** + * @see SubmissionController#submitDiagnosisKey(SubmissionPayload, String, String) + */ + @Test + void testTooManySrsPerDay() { + var response = executor.executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(OK); + when(diagnosisKeyService.countTodaysDiagnosisKeys()).thenReturn(config.getMaxKeysPerDay()); + response = executor.executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + assertThat(response.getStatusCode()).isEqualTo(TOO_MANY_REQUESTS); + } + + @Test + void testInvalidOtp() { + final HttpHeaders headers = buildSrsHeader(); + headers.set("cwa-otp", "this isn't an UUID"); + var response = executor.executePost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST), headers); + assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + @Test + void testValidCheckinData() { final long eventCheckinInThePast = ofInstant(Instant.now(), UTC).minusDays(9).toEpochSecond(UTC); final List validCheckinData = List.of( @@ -594,83 +655,16 @@ void testValidCheckinData() { .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInThePast) + 22) .setLocationId(EventCheckinDataValidatorTest.CORRECT_LOCATION_ID).build()); - final ResponseEntity actResponse = executor.executePost(buildPayloadWithCheckinData(validCheckinData)); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); - assertTraceWarningsHaveBeenSaved(validCheckinData.size() - + validCheckinData.size() * config.getRandomCheckinsPaddingMultiplier()); + final var response = executor.executePost(buildPayloadWithCheckinData(validCheckinData)); + assertThat(response.getStatusCode()).isEqualTo(OK); + assertTraceWarningsHaveBeenSaved( + validCheckinData.size() + validCheckinData.size() * config.getRandomCheckinsPaddingMultiplier()); } @ParameterizedTest @MethodSource("validVisitedCountries") void testValidVisitedCountriesSubmissionPayload(final List visitedCountries) { - final ResponseEntity actResponse = executor.executePost(buildPayloadWithVisitedCountries(visitedCountries)); - assertThat(actResponse.getStatusCode()).isEqualTo(OK); - } - - public static SubmissionPayload buildPayloadWithInvalidKey() { - final TemporaryExposureKey invalidKey = buildTemporaryExposureKey(VALID_KEY_DATA_1, - createRollingStartIntervalNumber(2), 999, ReportType.CONFIRMED_TEST, 1); - 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())); - } - - private static Stream invalidVisitedCountries() { - return Stream.of(Arguments.of(List.of("")), Arguments.of(List.of("D")), Arguments.of(List.of("FRE")), - Arguments.of(List.of("DE", "XX")), Arguments.of(List.of("DE", "FRE"))); - } - - private static Stream validVisitedCountries() { - return Stream.of(Arguments.of(List.of("DE")), Arguments.of(List.of("DE", "FR"))); - } - - /** - * @see SubmissionController#submitDiagnosisKey(SubmissionPayload, String, String) - */ - @Test - void testTooManySrsPerDay() { - ResponseEntity response = executor - .executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); - assertThat(response.getStatusCode()).isEqualTo(OK); - when(diagnosisKeyService.countTodaysSrs()).thenReturn(config.getMaxSrsPerDay()); - response = executor - .executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); - assertThat(response.getStatusCode()).isEqualTo(TOO_MANY_REQUESTS); - } - - /** - * @see SubmissionController#extractValidDiagnosisKeysFromPayload(SubmissionPayload, BodyBuilder) - */ - @Test - void testSrsKeysTruncated() { - final ResponseEntity response = executor.executeSrsPost( - // creates 3 keys around 14 days, which will be filtered and 3 more around the 'srsDays' - buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); + final var response = executor.executePost(buildPayloadWithVisitedCountries(visitedCountries)); assertThat(response.getStatusCode()).isEqualTo(OK); - assertThat(response.getHeaders()).containsKey(CWA_KEYS_TRUNCATED_HEADER); - assertThat(response.getHeaders()).containsEntry(CWA_KEYS_TRUNCATED_HEADER, List.of(valueOf(config.getSrsDays()))); - } - - /** - * @see SubmissionController#extractAndStoreDiagnosisKeys(SubmissionPayload, BodyBuilder) - */ - @Test - void testSrsKeysSendTwice() { - // fake existing keys - when(diagnosisKeyService.exists(any())).thenReturn(true); - // send again - ResponseEntity response = executor.executeSrsPost(buildSrsPayload(config, SUBMISSION_TYPE_SRS_SELF_TEST)); - assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST); } } diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 0e85de8712..594c8e7602 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -38,7 +38,7 @@ services: max-rolling-period: 144 min-rolling-period: 1 unencrypted-checkins-enabled: true - max-srs-per-day: 10 + max-keys-per-day: 10 payload: max-number-of-keys: 100 supported-countries: DE,FR,IT From aeec6cacbfbf7bd7aaf87e4e117cd4a342a05f8d Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 15:37:47 +0100 Subject: [PATCH 69/83] refactor --- .../integration/SubmissionPersistenceIT.java | 612 +++++++++--------- 1 file changed, 300 insertions(+), 312 deletions(-) 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 3ca4c19b0e..59dbf36516 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 @@ -2,6 +2,7 @@ import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_HOST_WARNING; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_PCR_TEST; +import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_RAPID_TEST; import static app.coronawarn.server.common.protocols.internal.SubmissionPayload.SubmissionType.SUBMISSION_TYPE_SRS_RAPID_PCR; import static app.coronawarn.server.services.submission.assertions.SubmissionAssertions.assertElementsCorrespondToEachOther; import static app.coronawarn.server.services.submission.controller.SubmissionController.SUBMISSION_ON_BEHALF_ROUTE; @@ -45,7 +46,6 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -80,8 +80,131 @@ class SubmissionPersistenceIT { private static final Logger logger = LoggerFactory.getLogger(SubmissionPersistenceIT.class); + public static final String PATH_MOBILE_CLIENT_PAYLOAD_PB = "src/test/resources/payload"; + public static final String FILENAME_MOBILE_CLIENT_PAYLOAD_PB = "mobile-client-payload.pb"; + + private static Stream invalidSubmissionPayload() { + return Stream.of( + Arguments.of(List.of("XX"), null, null), + Arguments.of(List.of("XX"), null, true), + Arguments.of(List.of("XX"), null, false), + Arguments.of(List.of("XX"), "DE", true), + Arguments.of(List.of("XX"), "DE", false), + Arguments.of(List.of("XX"), "DE", null), + Arguments.of(List.of("XX"), "IT", true), + Arguments.of(List.of("XX"), "IT", false), + Arguments.of(List.of("XX"), "IT", null), + Arguments.of(List.of("DE", "XX"), null, null), + Arguments.of(List.of("DE", "XX"), null, true), + Arguments.of(List.of("DE", "XX"), null, false), + Arguments.of(List.of("DE", "XX"), "DE", true), + Arguments.of(List.of("DE", "XX"), "DE", false), + Arguments.of(List.of("DE", "XX"), "DE", null), + Arguments.of(List.of("DE", "XX"), "IT", true), + Arguments.of(List.of("DE", "XX"), "IT", false), + Arguments.of(List.of("DE", "XX"), "IT", null), + Arguments.of(List.of(""), "", null), + Arguments.of(List.of(""), "", true), + Arguments.of(List.of(""), "", false), + Arguments.of(List.of(""), "DE", null), + Arguments.of(List.of(""), "DE", true), + Arguments.of(List.of(""), "DE", false), + Arguments.of(List.of(""), "IT", null), + Arguments.of(List.of(""), "IT", true), + Arguments.of(List.of(""), "IT", false), + Arguments.of(null, "RU", null), + Arguments.of(null, "RU", true), + Arguments.of(null, "RU", false), + Arguments.of(List.of("RU"), null, null), + Arguments.of(List.of("RU"), null, true), + Arguments.of(List.of("RU"), null, false), + Arguments.of(List.of("RU"), "RU", true), + Arguments.of(List.of("RU"), "RU", false), + Arguments.of(List.of("RU"), "RU", null), + Arguments.of(List.of("IT"), "RU", true), + Arguments.of(List.of("IT"), "RU", false), + Arguments.of(List.of("IT"), "RU", null), + Arguments.of(List.of("RU"), "IT", true), + Arguments.of(List.of("RU"), "IT", false), + Arguments.of(List.of("RU"), "IT", null)); + } + + private static Stream validSubmissionPayload() { + return Stream.of( + Arguments.of(null, null, null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(null, null, null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(null, null, true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(null, null, true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(null, null, false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(null, null, false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(null, "DE", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(null, "DE", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(null, "DE", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(null, "DE", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(null, "DE", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(null, "DE", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "DE", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "DE", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "DE", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "DE", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(emptyList(), "DE", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(emptyList(), "DE", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), "", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), "", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), "", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), "", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), "", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), "", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), "", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), "", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), "", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), "", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), "", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), "", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), null, false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), null, false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), "DE", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), "DE", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), "DE", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), "DE", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE"), "DE", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE"), "DE", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), null, null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), null, null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), null, true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), null, true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), null, false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), null, false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), "DE", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), "DE", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), "DE", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), "DE", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("IT"), "DE", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("IT"), "DE", null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE", "IT"), null, null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE", "IT"), null, null, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE", "IT"), null, true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE", "IT"), null, true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE", "IT"), null, false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE", "IT"), null, false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE", "IT"), "DE", true, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE", "IT"), "DE", true, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE", "IT"), "DE", false, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE", "IT"), "DE", false, SUBMISSION_TYPE_RAPID_TEST), + Arguments.of(List.of("DE", "IT"), "DE", null, SUBMISSION_TYPE_PCR_TEST), + Arguments.of(List.of("DE", "IT"), "DE", null, SUBMISSION_TYPE_RAPID_TEST)); + } + @Autowired private DiagnosisKeyService diagnosisKeyService; @@ -95,10 +218,10 @@ class SubmissionPersistenceIT { private JdbcTemplate jdbcTemplate; @Autowired - private TestRestTemplate testRestTemplate; + private TestRestTemplate rest; @Autowired - DiagnosisKeyRepository diagnosisKeyRepository; + private DiagnosisKeyRepository diagnosisKeyRepository; @MockBean private TanVerifier tanVerifier; @@ -112,42 +235,112 @@ class SubmissionPersistenceIT { @MockBean private FakeDelayManager fakeDelayManager; - @BeforeEach - public void setUpMocks() { - when(tanVerifier.verifyTan(anyString())).thenReturn(true); - when(srsOtpVerifier.verifyTan(anyString())).thenReturn(true); - when(eventTanVerifier.verifyTan(anyString())).thenReturn(true); - when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); + @Test + public void defaultsToPcrIfSubmissionTypeIsUndefined() throws IOException { + final List temporaryExposureKeys = createValidTemporaryExposureKeys(); + + final var payload1 = buildSubmissionPayload(emptyList(), "DE", true, temporaryExposureKeys, null); + + writeSubmissionPayloadProtobufFile(payload1); + + final Path path = Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); + final InputStream input = new FileInputStream(path.toFile()); + final SubmissionPayload payload = SubmissionPayload.parseFrom(input); + + logger.info("Submitting payload: " + System.lineSeparator() + + JsonFormat.printer().preservingProtoFieldNames().omittingInsignificantWhitespace().print(payload)); + + executor.executePost(payload); + + assertTrue(diagnosisKeyService.getDiagnosisKeys().stream() + .allMatch(diagnosisKey -> SUBMISSION_TYPE_PCR_TEST.equals(diagnosisKey.getSubmissionType()))); } - @Test - void testDiagnosisKeyRollingPeriodIsZeroShouldNotBeSaved() { + @ParameterizedTest + @MethodSource("invalidSubmissionPayload") + void failKeyInsertionWithMobileClientProtoBuf(final List visitedCountries, final String originCountry, + final Boolean consentToFederation) throws IOException { - // given - List invalidKeys = createValidTemporaryExposureKeys(0); + final List temporaryExposureKeys = createValidTemporaryExposureKeys(); - List validKeys = createValidTemporaryExposureKeys(1); + final SubmissionPayload submissionPayload = buildSubmissionPayload(visitedCountries, originCountry, + consentToFederation, temporaryExposureKeys, SUBMISSION_TYPE_PCR_TEST); - SubmissionPayload invalidSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - invalidKeys, SUBMISSION_TYPE_PCR_TEST); - String tan = "tan"; - final HttpHeaders headers = headers(); - headers.add("cwa-authorization", tan); + writeSubmissionPayloadProtobufFile(submissionPayload); - SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - validKeys, SUBMISSION_TYPE_PCR_TEST); + final Path path = Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); + final InputStream input = new FileInputStream(path.toFile()); + final SubmissionPayload payload = SubmissionPayload.parseFrom(input); - testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(invalidSubmissionPayload, headers), - Void.class); - assertEquals(0, diagnosisKeyRepository.count()); + executor.executePost(payload); - testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), Void.class); + assertEquals(0, diagnosisKeyService.getDiagnosisKeys().size()); + } - final int expectedNumberOfKeys = - validSubmissionPayload.getKeysList().size() * config.getRandomKeyPaddingMultiplier(); - assertEquals(expectedNumberOfKeys, diagnosisKeyRepository.count()); + private String generateDebugSqlStatement(final SubmissionPayload payload) { + final List base64Keys = payload.getKeysList() + .stream() + .map(key -> "'" + toBase64(key) + "'") + .toList(); + return "SELECT count(*) FROM diagnosis_key where ENCODE(key_data, 'BASE64') IN (" + + StringUtils.join(base64Keys, ',') + ")"; + } + + private HttpHeaders headers() { + final HttpHeaders headers = new HttpHeaders(); + headers.add("cwa-fake", "0"); + headers.setContentType(MediaType.valueOf("Application/x-protobuf")); + return headers; + } + + @ParameterizedTest + @MethodSource("validSubmissionPayload") + void okKeyInsertionWithMobileClientProtoBuf(final List visitedCountries, final String originCountry, + final Boolean consentToFederation, final SubmissionType submissionType) throws IOException { + + final List temporaryExposureKeys = createValidTemporaryExposureKeys(); + + final SubmissionPayload submissionPayload = buildSubmissionPayload(visitedCountries, originCountry, + consentToFederation, temporaryExposureKeys, submissionType); + + writeSubmissionPayloadProtobufFile(submissionPayload); + + final Path path = Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); + final InputStream input = new FileInputStream(path.toFile()); + final SubmissionPayload payload = SubmissionPayload.parseFrom(input); + + logger.info("Submitting payload: " + System.lineSeparator() + + JsonFormat.printer().preservingProtoFieldNames().omittingInsignificantWhitespace().print(payload)); + + executor.executePost(payload); + + final String presenceVerificationSql = generateDebugSqlStatement(payload); + logger.info("SQL debugging statement: " + System.lineSeparator() + presenceVerificationSql); + final Integer result = jdbcTemplate.queryForObject(presenceVerificationSql, Integer.class); + + final List expectedVisitedCountries = new ArrayList<>(payload.getVisitedCountriesList()); + expectedVisitedCountries.add(StringUtils + .defaultIfBlank(payload.getOrigin(), config.getDefaultOriginCountry())); + + final SubmissionPayload expectedPayload = SubmissionPayload.newBuilder() + .addAllKeys(payload.getKeysList()) + .setRequestPadding(payload.getRequestPadding()) + .addAllVisitedCountries(expectedVisitedCountries) + .setOrigin(StringUtils.defaultIfBlank(payload.getOrigin(), config.getDefaultOriginCountry())) + .setConsentToFederation(payload.getConsentToFederation()) + .setSubmissionType(submissionType) + .build(); + + assertEquals(payload.getKeysList().size(), result); + assertElementsCorrespondToEachOther(expectedPayload, diagnosisKeyService.getDiagnosisKeys(), config); + } + + @BeforeEach + public void setUpMocks() { + when(tanVerifier.verifyTan(anyString())).thenReturn(true); + when(srsOtpVerifier.verifyTan(anyString())).thenReturn(true); + when(eventTanVerifier.verifyTan(anyString())).thenReturn(true); + when(fakeDelayManager.getJitteredFakeDelay()).thenReturn(1000L); } /** @@ -156,12 +349,10 @@ void testDiagnosisKeyRollingPeriodIsZeroShouldNotBeSaved() { @Test void testBadRequest0() { final List validKeys = createValidTemporaryExposureKeys(1); - final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - validKeys, SUBMISSION_TYPE_SRS_RAPID_PCR); + final var payload = buildSubmissionPayload(List.of("DE"), "DE", true, validKeys, SUBMISSION_TYPE_SRS_RAPID_PCR); - final ResponseEntity response = testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers()), - Void.class); + final var response = rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(payload, headers()), + Void.class); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -177,11 +368,10 @@ void testBadRequest2() { headers.add("cwa-authorization", "foo"); headers.add("cwa-otp", "bar"); - final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - validKeys, SUBMISSION_TYPE_SRS_RAPID_PCR); + final var payload = buildSubmissionPayload(List.of("DE"), "DE", true, validKeys, SUBMISSION_TYPE_SRS_RAPID_PCR); - final ResponseEntity response = testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), Void.class); + final var response = rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(payload, headers), + Void.class); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -195,8 +385,8 @@ void testBadRequest3(final SubmissionType type) { final SubmissionPayload payload = buildSubmissionPayload(List.of("DE"), "DE", true, keys, type); - final ResponseEntity response = testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(payload, headers), Void.class); + final ResponseEntity response = rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, + new HttpEntity<>(payload, headers), Void.class); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -210,17 +400,58 @@ void testBadRequest4(final SubmissionType type) { final SubmissionPayload payload = buildSubmissionPayload(List.of("DE"), "DE", true, keys, type); - final ResponseEntity response = testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(payload, headers), Void.class); + final ResponseEntity response = rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, + new HttpEntity<>(payload, headers), Void.class); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } - private HttpHeaders headers() { - final HttpHeaders headers = new HttpHeaders(); - headers.add("cwa-fake", "0"); - headers.setContentType(MediaType.valueOf("Application/x-protobuf")); - return headers; + @Test + void testDiagnosisKeyRollingPeriodIsZeroShouldNotBeSaved() { + // given + final List invalidKeys = createValidTemporaryExposureKeys(0); + final List validKeys = createValidTemporaryExposureKeys(1); + + final var invalidPayload = buildSubmissionPayload(List.of("DE"), "DE", true, invalidKeys, SUBMISSION_TYPE_PCR_TEST); + final String tan = "tan"; + final HttpHeaders headers = headers(); + headers.add("cwa-authorization", tan); + + final var validPayload = buildSubmissionPayload(List.of("DE"), "DE", true, validKeys, SUBMISSION_TYPE_PCR_TEST); + + rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(invalidPayload, headers), Void.class); + assertEquals(0, diagnosisKeyRepository.count()); + + rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validPayload, headers), Void.class); + + final int expectedNumberOfKeys = validPayload.getKeysList().size() * config.getRandomKeyPaddingMultiplier(); + assertEquals(expectedNumberOfKeys, diagnosisKeyRepository.count()); + } + + @ParameterizedTest + @ValueSource(strings = { PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB }) + void testKeyInsertionWithMobileClientProtoBuf(final String testFile) throws IOException { + final List temporaryExposureKeys = createValidTemporaryExposureKeys(); + final SubmissionPayload submissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, temporaryExposureKeys, + SUBMISSION_TYPE_PCR_TEST); + + writeSubmissionPayloadProtobufFile(submissionPayload); + + final Path path = Paths.get(testFile); + final InputStream input = new FileInputStream(path.toFile()); + final SubmissionPayload payload = SubmissionPayload.parseFrom(input); + + logger.info("Submitting payload: " + System.lineSeparator() + + JsonFormat.printer().preservingProtoFieldNames().omittingInsignificantWhitespace().print(payload)); + + executor.executePost(payload); + + final String presenceVerificationSql = generateDebugSqlStatement(payload); + logger.info("SQL debugging statement: " + System.lineSeparator() + presenceVerificationSql); + final Integer result = jdbcTemplate.queryForObject(presenceVerificationSql, Integer.class); + + assertEquals(payload.getKeysList().size(), result); + assertElementsCorrespondToEachOther(payload, diagnosisKeyService.getDiagnosisKeys(), config); } /** @@ -233,14 +464,12 @@ void testSrsOtpAuth(final SubmissionType type) { final HttpHeaders headers = headers(); headers.add("cwa-otp", "bar"); - final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - validKeys, type); + final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, validKeys, type); final int before = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM self_report_submissions", Integer.class); - final ResponseEntity response = testRestTemplate - .postForEntity("/version/v1" + SUBMISSION_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), - Void.class); + final ResponseEntity response = rest.postForEntity("/version/v1" + SUBMISSION_ROUTE, + new HttpEntity<>(validSubmissionPayload, headers), Void.class); assertEquals(HttpStatus.OK, response.getStatusCode()); final int result = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM self_report_submissions", Integer.class); @@ -250,12 +479,13 @@ void testSrsOtpAuth(final SubmissionType type) { @Test @SuppressWarnings("deprecation") void testSubmissionOnBehalf() { - byte[] locationIdHash = new byte[32]; + final byte[] locationIdHash = new byte[32]; new Random().nextBytes(locationIdHash); - List protectedReports = List.of(DataHelpers.buildDefaultEncryptedCheckIn(locationIdHash), + final List protectedReports = List.of( + DataHelpers.buildDefaultEncryptedCheckIn(locationIdHash), DataHelpers.buildDefaultEncryptedCheckIn(locationIdHash)); - List checkIns = List.of(DataHelpers.buildDefaultCheckIn(), DataHelpers.buildDefaultCheckIn()); - SubmissionPayload validSubmissionPayload = SubmissionPayload.newBuilder().addAllKeys(Collections.emptyList()) + final List checkIns = List.of(DataHelpers.buildDefaultCheckIn(), DataHelpers.buildDefaultCheckIn()); + final SubmissionPayload validSubmissionPayload = SubmissionPayload.newBuilder().addAllKeys(Collections.emptyList()) .addAllVisitedCountries(Collections.emptyList()).setConsentToFederation(false) .setSubmissionType(SUBMISSION_TYPE_HOST_WARNING).addAllCheckInProtectedReports(protectedReports) .addAllCheckIns(checkIns).build(); @@ -265,7 +495,7 @@ void testSubmissionOnBehalf() { final int before = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM check_in_protected_reports", Integer.class); final int none = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM diagnosis_key", Integer.class); - final ResponseEntity response = testRestTemplate.postForEntity("/version/v1" + SUBMISSION_ON_BEHALF_ROUTE, + final ResponseEntity response = rest.postForEntity("/version/v1" + SUBMISSION_ON_BEHALF_ROUTE, new HttpEntity<>(validSubmissionPayload, headers), Void.class); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -274,255 +504,26 @@ void testSubmissionOnBehalf() { assertEquals(none, jdbcTemplate.queryForObject("SELECT COUNT(*) FROM diagnosis_key", Integer.class)); } - @ParameterizedTest - @ValueSource(strings = { PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB }) - void testKeyInsertionWithMobileClientProtoBuf(String testFile) throws IOException { - List temporaryExposureKeys = createValidTemporaryExposureKeys(); - SubmissionPayload submissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, - temporaryExposureKeys, SubmissionType.SUBMISSION_TYPE_PCR_TEST); - - writeSubmissionPayloadProtobufFile(submissionPayload); - - 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, diagnosisKeyService.getDiagnosisKeys(), config); - } - - @ParameterizedTest - @MethodSource("validSubmissionPayload") - void okKeyInsertionWithMobileClientProtoBuf(List visitedCountries, String originCountry, - Boolean consentToFederation, SubmissionType submissionType) throws IOException { - - List temporaryExposureKeys = createValidTemporaryExposureKeys(); - - SubmissionPayload submissionPayload = buildSubmissionPayload(visitedCountries, originCountry, consentToFederation, - temporaryExposureKeys, submissionType); - - writeSubmissionPayloadProtobufFile(submissionPayload); - - Path path = Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); - 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); - - List expectedVisitedCountries = new ArrayList<>(payload.getVisitedCountriesList()); - expectedVisitedCountries.add(StringUtils - .defaultIfBlank(payload.getOrigin(), config.getDefaultOriginCountry())); - - SubmissionPayload expectedPayload = SubmissionPayload.newBuilder() - .addAllKeys(payload.getKeysList()) - .setRequestPadding(payload.getRequestPadding()) - .addAllVisitedCountries(expectedVisitedCountries) - .setOrigin(StringUtils.defaultIfBlank(payload.getOrigin(), config.getDefaultOriginCountry())) - .setConsentToFederation(payload.getConsentToFederation()) - .setSubmissionType(submissionType) - .build(); - - assertEquals(payload.getKeysList().size(), result); - assertElementsCorrespondToEachOther(expectedPayload, diagnosisKeyService.getDiagnosisKeys(), config); - } - - @Test - public void defaultsToPcrIfSubmissionTypeIsUndefined() throws IOException { - List temporaryExposureKeys = createValidTemporaryExposureKeys(); - - SubmissionPayload submissionPayload = buildSubmissionPayload(emptyList(), "DE", true, temporaryExposureKeys, null); - - writeSubmissionPayloadProtobufFile(submissionPayload); - - Path path = Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); - 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); - - assertTrue(diagnosisKeyService.getDiagnosisKeys().stream() - .allMatch(diagnosisKey -> diagnosisKey.getSubmissionType().equals(SubmissionType.SUBMISSION_TYPE_PCR_TEST))); - } - - private static Stream validSubmissionPayload() { - return Stream.of( - Arguments.of(null, null, null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(null, null, null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(null, null, true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(null, null, true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(null, null, false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(null, null, false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(null, "DE", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(null, "DE", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(null, "DE", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(null, "DE", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(null, "DE", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(null, "DE", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "DE", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "DE", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "DE", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "DE", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(emptyList(), "DE", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(emptyList(), "DE", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), "", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), "", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), "", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), "", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), "", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), "", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), "", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), "", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), "", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), "", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), "", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), "", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), null, false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), null, false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), "DE", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), "DE", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), "DE", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), "DE", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE"), "DE", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE"), "DE", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), null, null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), null, null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), null, true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), null, true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), null, false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), null, false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), "DE", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), "DE", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), "DE", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), "DE", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("IT"), "DE", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("IT"), "DE", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE", "IT"), null, null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE", "IT"), null, null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE", "IT"), null, true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE", "IT"), null, true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE", "IT"), null, false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE", "IT"), null, false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE", "IT"), "DE", true, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE", "IT"), "DE", true, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE", "IT"), "DE", false, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE", "IT"), "DE", false, SubmissionType.SUBMISSION_TYPE_RAPID_TEST), - Arguments.of(List.of("DE", "IT"), "DE", null, SubmissionType.SUBMISSION_TYPE_PCR_TEST), - Arguments.of(List.of("DE", "IT"), "DE", null, SubmissionType.SUBMISSION_TYPE_RAPID_TEST) - ); - } - - @ParameterizedTest - @MethodSource("invalidSubmissionPayload") - void failKeyInsertionWithMobileClientProtoBuf(List visitedCountries, String originCountry, - Boolean consentToFederation) throws IOException { - - List temporaryExposureKeys = createValidTemporaryExposureKeys(); - - SubmissionPayload submissionPayload = buildSubmissionPayload(visitedCountries, originCountry, consentToFederation, - temporaryExposureKeys, SubmissionType.SUBMISSION_TYPE_PCR_TEST); - - writeSubmissionPayloadProtobufFile(submissionPayload); - - Path path = Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); - InputStream input = new FileInputStream(path.toFile()); - SubmissionPayload payload = SubmissionPayload.parseFrom(input); - - executor.executePost(payload); - - assertEquals(0, diagnosisKeyService.getDiagnosisKeys().size()); - } - - private static Stream invalidSubmissionPayload() { - return Stream.of( - Arguments.of(List.of("XX"), null, null), - Arguments.of(List.of("XX"), null, true), - Arguments.of(List.of("XX"), null, false), - Arguments.of(List.of("XX"), "DE", true), - Arguments.of(List.of("XX"), "DE", false), - Arguments.of(List.of("XX"), "DE", null), - Arguments.of(List.of("XX"), "IT", true), - Arguments.of(List.of("XX"), "IT", false), - Arguments.of(List.of("XX"), "IT", null), - Arguments.of(List.of("DE", "XX"), null, null), - Arguments.of(List.of("DE", "XX"), null, true), - Arguments.of(List.of("DE", "XX"), null, false), - Arguments.of(List.of("DE", "XX"), "DE", true), - Arguments.of(List.of("DE", "XX"), "DE", false), - Arguments.of(List.of("DE", "XX"), "DE", null), - Arguments.of(List.of("DE", "XX"), "IT", true), - Arguments.of(List.of("DE", "XX"), "IT", false), - Arguments.of(List.of("DE", "XX"), "IT", null), - Arguments.of(List.of(""), "", null), - Arguments.of(List.of(""), "", true), - Arguments.of(List.of(""), "", false), - Arguments.of(List.of(""), "DE", null), - Arguments.of(List.of(""), "DE", true), - Arguments.of(List.of(""), "DE", false), - Arguments.of(List.of(""), "IT", null), - Arguments.of(List.of(""), "IT", true), - Arguments.of(List.of(""), "IT", false), - Arguments.of(null, "RU", null), - Arguments.of(null, "RU", true), - Arguments.of(null, "RU", false), - Arguments.of(List.of("RU"), null, null), - Arguments.of(List.of("RU"), null, true), - Arguments.of(List.of("RU"), null, false), - Arguments.of(List.of("RU"), "RU", true), - Arguments.of(List.of("RU"), "RU", false), - Arguments.of(List.of("RU"), "RU", null), - Arguments.of(List.of("IT"), "RU", true), - Arguments.of(List.of("IT"), "RU", false), - Arguments.of(List.of("IT"), "RU", null), - Arguments.of(List.of("RU"), "IT", true), - Arguments.of(List.of("RU"), "IT", false), - Arguments.of(List.of("RU"), "IT", null) - ); + private String toBase64(final TemporaryExposureKey key) { + return BaseEncoding.base64().encode(key.getKeyData().toByteArray()); } @Deprecated @Test void unencryptedCheckInsEnabledShouldResultInSavingCorrectNumberOfCheckIns() { // GIVEN: - List visitedCountries = Collections.singletonList("DE"); - String originCountry = "DE"; + final List visitedCountries = Collections.singletonList("DE"); + final String originCountry = "DE"; - List temporaryExposureKeys = createValidTemporaryExposureKeys(); - List protectedReports = Collections.singletonList(buildDefaultEncryptedCheckIn()); - List checkins = Collections.singletonList( + final List temporaryExposureKeys = createValidTemporaryExposureKeys(); + final List protectedReports = Collections.singletonList(buildDefaultEncryptedCheckIn()); + final List checkins = Collections.singletonList( buildDefaultCheckIn()); - SubmissionPayload submissionPayload = buildSubmissionPayloadWithCheckins(visitedCountries, originCountry, - true, - temporaryExposureKeys, SubmissionType.SUBMISSION_TYPE_PCR_TEST, protectedReports, checkins); + final SubmissionPayload submissionPayload = buildSubmissionPayloadWithCheckins(visitedCountries, originCountry, + true, temporaryExposureKeys, SUBMISSION_TYPE_PCR_TEST, protectedReports, checkins); // WHEN: - ResponseEntity result = executor.executePost(submissionPayload); + final ResponseEntity result = executor.executePost(submissionPayload); // THEN: // For the one valid unencrypted checkin we generate one fake checkins and we also save the encrypted checkins @@ -531,22 +532,9 @@ void unencryptedCheckInsEnabledShouldResultInSavingCorrectNumberOfCheckIns() { assertThat(result.getHeaders().get("cwa-saved-checkins").get(0)).isEqualTo("3"); } - 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()); - } - - private void writeSubmissionPayloadProtobufFile(SubmissionPayload submissionPayload) throws IOException { + private void writeSubmissionPayloadProtobufFile(final SubmissionPayload submissionPayload) throws IOException { Files.createDirectories(Paths.get(PATH_MOBILE_CLIENT_PAYLOAD_PB)); - File file = new File(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); + final File file = new File(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB); file.createNewFile(); submissionPayload .writeTo(new FileOutputStream(PATH_MOBILE_CLIENT_PAYLOAD_PB + "/" + FILENAME_MOBILE_CLIENT_PAYLOAD_PB)); From 6ad592b23f7244d291dd0981f039724aa6ea87d5 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 16:01:37 +0100 Subject: [PATCH 70/83] pass UUID validation check --- .../submission/integration/SubmissionPersistenceIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 59dbf36516..9281fe48b3 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 @@ -46,6 +46,7 @@ import java.util.Collections; import java.util.List; import java.util.Random; +import java.util.UUID; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -462,7 +463,7 @@ void testKeyInsertionWithMobileClientProtoBuf(final String testFile) throws IOEx void testSrsOtpAuth(final SubmissionType type) { final List validKeys = createValidTemporaryExposureKeys(1); final HttpHeaders headers = headers(); - headers.add("cwa-otp", "bar"); + headers.add("cwa-otp", UUID.randomUUID().toString()); final SubmissionPayload validSubmissionPayload = buildSubmissionPayload(List.of("DE"), "DE", true, validKeys, type); From 88b691722c5591507906034453acb048d3f8fe4b Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 16:58:00 +0100 Subject: [PATCH 71/83] add header "cwa-error-code": "KEYS_ALREADY_EXIST" --- .../submission/controller/SubmissionController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 496ee374ab..92dea12ccf 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -201,6 +201,10 @@ public static DeferredResult> badRequest() { return new DeferredResult<>(null, () -> ResponseEntity.badRequest().build()); } + public static DeferredResult> badRequest(final String errorDetails) { + return new DeferredResult<>(null, () -> ResponseEntity.badRequest().header("cwa-error-code", errorDetails).build()); + } + public static DeferredResult> tooManyRequests() { return new DeferredResult<>(null, () -> ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build()); } @@ -265,7 +269,7 @@ private DeferredResult> buildRealDeferredResult(final Submi } catch (final DiagnosisKeyExistsAlreadyException e) { logger.warn(SECURITY, "Self-Report contains already persisted keys - {}", new PrintableSubmissionPayload(payload)); - return badRequest(); + return badRequest("KEYS_ALREADY_EXIST"); } catch (Exception e) { logger.error(e.getLocalizedMessage(), e); deferredResult.setErrorResult(e); From 9940fce11c5d28f15fef67cdf1b6bf34e15b2c0f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 17:10:22 +0100 Subject: [PATCH 72/83] feign timeouts 20 seconds --- services/submission/src/main/resources/application.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index 921b9e2ed0..fa0a960af1 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -149,5 +149,5 @@ feign: client: config: default: - connect-timeout: 5000 - read-timeout: 5000 + connect-timeout: 20000 + read-timeout: 20000 From c80adcaa6f70159d617ebebd6cd1db13568cd166 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 17:10:56 +0100 Subject: [PATCH 73/83] feign debug loggerLevel FULL --- .../submission/src/main/resources/application-debug.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/submission/src/main/resources/application-debug.yaml b/services/submission/src/main/resources/application-debug.yaml index e437ef440a..bec1a3101d 100644 --- a/services/submission/src/main/resources/application-debug.yaml +++ b/services/submission/src/main/resources/application-debug.yaml @@ -1,4 +1,3 @@ ---- logging: level: org: @@ -6,3 +5,8 @@ logging: web: DEBUG app: coronawarn: DEBUG +feign: + client: + config: + default: + loggerLevel: FULL \ No newline at end of file From 684ae3078b745b2b3857f63a05819d4e6be3504a Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Thu, 15 Dec 2022 17:11:32 +0100 Subject: [PATCH 74/83] add our own feign.Retryer TODO make values configurable --- .../verification/CloudFeignClientProvider.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java index 0e2da8cfb3..848b681d1c 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java @@ -4,10 +4,12 @@ import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig.Client.Ssl; import feign.Client; +import feign.Retryer; import feign.httpclient.ApacheHttpClient; import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; +import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.ssl.SSLContextBuilder; @@ -81,4 +83,13 @@ public ApacheHttpClientFactory createHttpClientFactory() { public ApacheHttpClientConnectionManagerFactory createConnectionManager() { return new DefaultApacheHttpClientConnectionManagerFactory(); } + + @Bean + public Retryer retryer() { + // FIXME make it configurable + long retryPeriod = TimeUnit.SECONDS.toMillis(1); + long maxRetryPeriod = TimeUnit.SECONDS.toMillis(10); + int maxAttempts = 10; + return new Retryer.Default(retryPeriod, maxRetryPeriod, maxAttempts); + } } From 9fc5f89a1a3eb33730124273b1f2bcb42c2cc727 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 14:12:58 +0100 Subject: [PATCH 75/83] Revert "add our own feign.Retryer" This reverts commit 684ae3078b745b2b3857f63a05819d4e6be3504a. --- .../verification/CloudFeignClientProvider.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java index 848b681d1c..0e2da8cfb3 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java @@ -4,12 +4,10 @@ import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig.Client.Ssl; import feign.Client; -import feign.Retryer; import feign.httpclient.ApacheHttpClient; import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; -import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.ssl.SSLContextBuilder; @@ -83,13 +81,4 @@ public ApacheHttpClientFactory createHttpClientFactory() { public ApacheHttpClientConnectionManagerFactory createConnectionManager() { return new DefaultApacheHttpClientConnectionManagerFactory(); } - - @Bean - public Retryer retryer() { - // FIXME make it configurable - long retryPeriod = TimeUnit.SECONDS.toMillis(1); - long maxRetryPeriod = TimeUnit.SECONDS.toMillis(10); - int maxAttempts = 10; - return new Retryer.Default(retryPeriod, maxRetryPeriod, maxAttempts); - } } From 7910bb111741f2c9b22292398337600b7c3f1a2f Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 14:15:04 +0100 Subject: [PATCH 76/83] deferredResult.setResult - instead of creating a new one --- .../controller/SubmissionController.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 92dea12ccf..25b0c4f462 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -6,6 +6,9 @@ import static java.lang.String.valueOf; import static java.time.LocalDate.now; import static java.time.ZoneOffset.UTC; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.TOO_MANY_REQUESTS; import static org.springframework.util.ObjectUtils.isEmpty; import app.coronawarn.server.common.persistence.domain.DiagnosisKey; @@ -39,7 +42,6 @@ import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.MarkerFactory; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity.BodyBuilder; import org.springframework.util.StopWatch; @@ -201,12 +203,8 @@ public static DeferredResult> badRequest() { return new DeferredResult<>(null, () -> ResponseEntity.badRequest().build()); } - public static DeferredResult> badRequest(final String errorDetails) { - return new DeferredResult<>(null, () -> ResponseEntity.badRequest().header("cwa-error-code", errorDetails).build()); - } - public static DeferredResult> tooManyRequests() { - return new DeferredResult<>(null, () -> ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build()); + return new DeferredResult<>(null, () -> ResponseEntity.status(TOO_MANY_REQUESTS).build()); } /** @@ -245,7 +243,7 @@ private DeferredResult> buildRealDeferredResult(final Submi try { if (!tanVerifier.verifyTan(tan)) { submissionMonitor.incrementInvalidTanRequestCounter(); - deferredResult.setResult(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); + deferredResult.setResult(ResponseEntity.status(FORBIDDEN).build()); } else { final BodyBuilder response = ResponseEntity.ok(); extractAndStoreDiagnosisKeys(payload, response); @@ -260,17 +258,18 @@ private DeferredResult> buildRealDeferredResult(final Submi .header(CWA_SAVED_CHECKINS_HEADER, valueOf(checkinsStorageResult.getNumberOfSavedCheckins())); deferredResult.setResult(response.build()); } - } catch (RetryableException e) { + } catch (final RetryableException e) { logger.error("Verification Service could not be reached after retry mechanism.", e); deferredResult.setErrorResult(e); - } catch (FeignException e) { + } catch (final FeignException e) { logger.error("Verification Service could not be reached.", e); deferredResult.setErrorResult(e); } catch (final DiagnosisKeyExistsAlreadyException e) { logger.warn(SECURITY, "Self-Report contains already persisted keys - {}", new PrintableSubmissionPayload(payload)); - return badRequest("KEYS_ALREADY_EXIST"); - } catch (Exception e) { + deferredResult + .setResult(ResponseEntity.status(BAD_REQUEST).header("cwa-error-code", "KEYS_ALREADY_EXIST").build()); + } catch (final Exception e) { logger.error(e.getLocalizedMessage(), e); deferredResult.setErrorResult(e); } finally { From f5185f3ec3b942adf4f60aa2010df34372fa800b Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 15:06:18 +0100 Subject: [PATCH 77/83] add assertion condition --- .../submission/controller/SubmissionControllerTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 49348d786e..d8d7165aac 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 @@ -456,8 +456,9 @@ void submmissionPayloadWithInvalidIntervalItemsIsFilteredCorrectly() { final Collection savedDiagnosisKeys = argument.getValue(); final int expectedNumberofSavedKeys = (submittedKeys.size() - 1) * config.getRandomKeyPaddingMultiplier(); assertThat(savedDiagnosisKeys).hasSize(expectedNumberofSavedKeys); - assertThat(!savedDiagnosisKeys.stream().anyMatch(diagnosisKey -> diagnosisKey - .getRollingStartIntervalNumber() == futureKey.getRollingStartIntervalNumber())); + assertThat(savedDiagnosisKeys.stream().anyMatch( + diagnosisKey -> diagnosisKey.getRollingStartIntervalNumber() == futureKey.getRollingStartIntervalNumber())) + .isFalse(); } @Test From 31949f897e6a6880a9050dc1ae0640d0ff227677 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 15:49:31 +0100 Subject: [PATCH 78/83] feign Retry configuration --- .../config/SubmissionServiceConfig.java | 659 ++++++++++-------- .../CloudFeignClientProvider.java | 68 +- .../src/main/resources/application.yaml | 9 +- .../src/test/resources/application.yaml | 7 + 4 files changed, 424 insertions(+), 319 deletions(-) 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 a24c1c4c91..3438c0473c 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 @@ -20,211 +20,407 @@ @Validated public class SubmissionServiceConfig { + public static class Client { + + public static class Ssl { + + private String keyPassword; + + private File keyStore; + + private String keyStorePassword; + + private File trustStore; + + private String trustStorePassword; + + public String getKeyPassword() { + return keyPassword; + } + + public File getKeyStore() { + return keyStore; + } + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public File getTrustStore() { + return trustStore; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public void setKeyPassword(final String keyPassword) { + this.keyPassword = keyPassword; + } + + public void setKeyStore(final File keyStore) { + this.keyStore = keyStore; + } + + public void setKeyStorePassword(final String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public void setTrustStore(final File trustStore) { + this.trustStore = trustStore; + } + + public void setTrustStorePassword(final String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + } + + private Ssl ssl; + + public Ssl getSsl() { + return ssl; + } + + public void setSsl(final Ssl ssl) { + this.ssl = ssl; + } + } + + public static class FeignRetry { + + @Min(1) + @Max(100) + private int maxAttempts; + + @Min(1) + @Max(120) + private long maxPeriod; + + @Min(100) + @Max(10000) + private long period; + + public int getMaxAttempts() { + return maxAttempts; + } + + public long getMaxPeriod() { + return maxPeriod; + } + + public long getPeriod() { + return period; + } + + public void setMaxAttempts(final int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + public void setMaxPeriod(final long maxPeriod) { + this.maxPeriod = maxPeriod; + } + + public void setPeriod(final long period) { + this.period = period; + } + } + + private static class Monitoring { + + @Min(1) + @Max(1000) + private Long batchSize; + + public Long getBatchSize() { + return batchSize; + } + + public void setBatchSize(final Long batchSize) { + this.batchSize = batchSize; + } + } + + public static class Payload { + + public static class Checkins { + + @NotNull + private Integer acceptedEventDateThresholdDays; + + public Integer getAcceptedEventDateThresholdDays() { + return acceptedEventDateThresholdDays; + } + + public void setAcceptedEventDateThresholdDays(final Integer acceptedEventDateThresholdDays) { + this.acceptedEventDateThresholdDays = acceptedEventDateThresholdDays; + } + } + + private Checkins checkins; + + private String defaultOriginCountry; + + @Min(7) + @Max(100) + private Integer maxNumberOfKeys; + + @NotEmpty + private String[] supportedCountries; + + public Checkins getCheckins() { + return checkins; + } + + public String getDefaultOriginCountry() { + return defaultOriginCountry; + } + + public Integer getMaxNumberOfKeys() { + return maxNumberOfKeys; + } + + public String[] getSupportedCountries() { + return supportedCountries; + } + + public void setCheckins(final Checkins checkins) { + this.checkins = checkins; + } + + public void setDefaultOriginCountry(final String defaultOriginCountry) { + this.defaultOriginCountry = defaultOriginCountry; + } + + public void setMaxNumberOfKeys(final Integer maxNumberOfKeys) { + this.maxNumberOfKeys = maxNumberOfKeys; + } + + public void setSupportedCountries(final String[] supportedCountries) { + this.supportedCountries = supportedCountries; + } + } + + static class Verification { + + @Pattern(regexp = URL_WITH_PORT_REGEX) + private String baseUrl; + + @Pattern(regexp = PATH_REGEX) + private String path; + + public String getBaseUrl() { + return baseUrl; + } + + public String getPath() { + return path; + } + + public void setBaseUrl(final String baseUrl) { + this.baseUrl = baseUrl; + } + + public void setPath(final String path) { + this.path = path; + } + } + private static final String PATH_REGEX = "^[/]?[a-zA-Z0-9_]{1,1024}(/[a-zA-Z0-9_]{1,1024}){0,256}[/]?$"; - private static final String URL_WITH_PORT_REGEX = - "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; - // Exponential moving average of the last N real request durations (in ms), where - // N = fakeDelayMovingAverageSamples. + private static final String URL_WITH_PORT_REGEX = "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; + + private Client client; + @Min(1) - @Max(3000) - private Long initialFakeDelayMilliseconds; + @Max(10000) + private Integer connectionPoolSize; + + /** + * Exponential moving average of the last N real request durations (in ms), where N = fakeDelayMovingAverageSamples. + */ @Min(1) @Max(100) private Long fakeDelayMovingAverageSamples; - @Min(7) - @Max(28) - private Integer retentionDays; - @Min(0) - @Max(28) - private int srsDays; - @Min(1) - @Max(25) - private Integer randomKeyPaddingMultiplier; + + private FeignRetry feignRetry; + @Min(1) - @Max(25) - private Integer randomCheckinsPaddingMultiplier; + @Max(3000) + private Long initialFakeDelayMilliseconds; + @Min(1) @Max(9999) private Integer maxAllowedCheckinsPerDay; - @NotEmpty - private String randomCheckinsPaddingPepper; - @Min(1) - @Max(10000) - private Integer connectionPoolSize; + private DataSize maximumRequestSize; - private Payload payload; - private Verification verification; - private Verification srsVerify; - private Monitoring monitoring; - private Client client; + + @Min(1) + @Max(100000000) + private int maxKeysPerDay; + @Min(1) @Max(144) private Integer maxRollingPeriod; + @Min(1) @Max(144) private Integer minRollingPeriod; - @Min(1) - @Max(100000000) - private int maxKeysPerDay; - /** - * unencryptedCheckinsEnabled. - * - * @deprecated should be removed when false - */ - @Deprecated(since = "2.8", forRemoval = false) - private Boolean unencryptedCheckinsEnabled; + private Monitoring monitoring; - @Autowired - private TekFieldDerivations tekFieldDerivations; + private Payload payload; - @Autowired - private TrlDerivations trlDerivations; + @Min(1) + @Max(25) + private Integer randomCheckinsPaddingMultiplier; + @NotEmpty + private String randomCheckinsPaddingPepper; - public Long getInitialFakeDelayMilliseconds() { - return initialFakeDelayMilliseconds; - } + @Min(1) + @Max(25) + private Integer randomKeyPaddingMultiplier; - public void setInitialFakeDelayMilliseconds(Long initialFakeDelayMilliseconds) { - this.initialFakeDelayMilliseconds = initialFakeDelayMilliseconds; - } + @Min(7) + @Max(28) + private Integer retentionDays; - public Long getFakeDelayMovingAverageSamples() { - return fakeDelayMovingAverageSamples; - } + @Min(0) + @Max(28) + private int srsDays; - public void setFakeDelayMovingAverageSamples(Long fakeDelayMovingAverageSamples) { - this.fakeDelayMovingAverageSamples = fakeDelayMovingAverageSamples; - } + private Verification srsVerify; - public Integer getRetentionDays() { - return retentionDays; - } + @Autowired + private TekFieldDerivations tekFieldDerivations; - public void setRetentionDays(Integer retentionDays) { - this.retentionDays = retentionDays; - } + @Autowired + private TrlDerivations trlDerivations; - public int getSrsDays() { - return Math.min(srsDays, retentionDays); - } + /** + * unencryptedCheckinsEnabled. + * + * @deprecated should be removed when false + */ + @Deprecated(since = "2.8", forRemoval = false) + private Boolean unencryptedCheckinsEnabled; - public void setSrsDays(int srsDays) { - this.srsDays = srsDays; - } + private Verification verification; - public Integer getRandomKeyPaddingMultiplier() { - return randomKeyPaddingMultiplier; + public Integer getAcceptedEventDateThresholdDays() { + return payload.checkins.getAcceptedEventDateThresholdDays(); } - public void setRandomKeyPaddingMultiplier(Integer randomKeyPaddingMultiplier) { - this.randomKeyPaddingMultiplier = randomKeyPaddingMultiplier; + public Client getClient() { + return client; } - public Integer getRandomCheckinsPaddingMultiplier() { - return randomCheckinsPaddingMultiplier; + public Integer getConnectionPoolSize() { + return connectionPoolSize; } - public void setRandomCheckinsPaddingMultiplier(Integer randomCheckinsPaddingMultiplier) { - this.randomCheckinsPaddingMultiplier = randomCheckinsPaddingMultiplier; + public String getDefaultOriginCountry() { + return payload.defaultOriginCountry; } - public String getRandomCheckinsPaddingPepper() { - return randomCheckinsPaddingPepper; + public Long getFakeDelayMovingAverageSamples() { + return fakeDelayMovingAverageSamples; } - public byte[] getRandomCheckinsPaddingPepperAsByteArray() { - return Hex.decode(randomCheckinsPaddingPepper.getBytes()); + public FeignRetry getFeignRetry() { + return feignRetry; } - public void setRandomCheckinsPaddingPepper(String randomCheckinsPaddingPepper) { - this.randomCheckinsPaddingPepper = randomCheckinsPaddingPepper; + public Long getInitialFakeDelayMilliseconds() { + return initialFakeDelayMilliseconds; } public Integer getMaxAllowedCheckinsPerDay() { return maxAllowedCheckinsPerDay; } - public void setMaxAllowedCheckinsPerDay(Integer maxAllowedCheckinsPerDay) { - this.maxAllowedCheckinsPerDay = maxAllowedCheckinsPerDay; + public DataSize getMaximumRequestSize() { + return maximumRequestSize; } - public Integer getConnectionPoolSize() { - return connectionPoolSize; + public int getMaxKeysPerDay() { + return maxKeysPerDay; } - public void setConnectionPoolSize(Integer connectionPoolSize) { - this.connectionPoolSize = connectionPoolSize; + public Integer getMaxNumberOfKeys() { + return payload.getMaxNumberOfKeys(); } - public DataSize getMaximumRequestSize() { - return maximumRequestSize; + public Integer getMaxRollingPeriod() { + return maxRollingPeriod; } - public void setMaximumRequestSize(DataSize maximumRequestSize) { - this.maximumRequestSize = maximumRequestSize; + public Integer getMinRollingPeriod() { + return minRollingPeriod; } - public Integer getMaxRollingPeriod() { - return maxRollingPeriod; + public Monitoring getMonitoring() { + return monitoring; } - public void setMaxRollingPeriod(Integer maxRollingPeriod) { - this.maxRollingPeriod = maxRollingPeriod; + public Long getMonitoringBatchSize() { + return monitoring.getBatchSize(); } - public Integer getMinRollingPeriod() { - return minRollingPeriod; + public Integer getRandomCheckinsPaddingMultiplier() { + return randomCheckinsPaddingMultiplier; } - public void setMinRollingPeriod(Integer minRollingPeriod) { - this.minRollingPeriod = minRollingPeriod; + public String getRandomCheckinsPaddingPepper() { + return randomCheckinsPaddingPepper; } - public Integer getMaxNumberOfKeys() { - return payload.getMaxNumberOfKeys(); + public byte[] getRandomCheckinsPaddingPepperAsByteArray() { + return Hex.decode(randomCheckinsPaddingPepper.getBytes()); } - public String getDefaultOriginCountry() { - return payload.defaultOriginCountry; + public Integer getRandomKeyPaddingMultiplier() { + return randomKeyPaddingMultiplier; } - public String[] getSupportedCountries() { - return payload.getSupportedCountries(); + public Integer getRetentionDays() { + return retentionDays; } - public void setSupportedCountries(String[] supportedCountries) { - payload.setSupportedCountries(supportedCountries); + public int getSrsDays() { + return Math.min(srsDays, retentionDays); } - public Integer getAcceptedEventDateThresholdDays() { - return payload.checkins.getAcceptedEventDateThresholdDays(); + Verification getSrsVerify() { + return srsVerify; } - public void setAcceptedEventDateThresholdDays(Integer acceptedEventDateThresholdDays) { - this.payload.checkins.setAcceptedEventDateThresholdDays(acceptedEventDateThresholdDays); + public String getSrsVerifyBaseUrl() { + return getSrsVerify().getBaseUrl(); } - public void setPayload(Payload payload) { - this.payload = payload; + public String getSrsVerifyPath() { + return getSrsVerify().getPath(); } - public TekFieldDerivations getTekFieldDerivations() { - return tekFieldDerivations; + public String[] getSupportedCountries() { + return payload.getSupportedCountries(); } - public void setTekFieldDerivations(TekFieldDerivations tekFieldDerivations) { - this.tekFieldDerivations = tekFieldDerivations; + public TekFieldDerivations getTekFieldDerivations() { + return tekFieldDerivations; } public TrlDerivations getTrlDerivations() { return trlDerivations; } - public void setTrlDerivations(TrlDerivations trlDerivations) { - this.trlDerivations = trlDerivations; + public String getVerificationBaseUrl() { + return verification.getBaseUrl(); + } + + public String getVerificationPath() { + return verification.getPath(); } /** @@ -237,226 +433,103 @@ public Boolean isUnencryptedCheckinsEnabled() { return unencryptedCheckinsEnabled == null ? false : unencryptedCheckinsEnabled; } - public void setUnencryptedCheckinsEnabled(Boolean unencryptedCheckinsEnabled) { - this.unencryptedCheckinsEnabled = unencryptedCheckinsEnabled; + public void setAcceptedEventDateThresholdDays(final Integer acceptedEventDateThresholdDays) { + payload.checkins.setAcceptedEventDateThresholdDays(acceptedEventDateThresholdDays); } - public int getMaxKeysPerDay() { - return maxKeysPerDay; + public void setClient(final Client client) { + this.client = client; } - public void setMaxKeysPerDay(final int maxKeysPerDay) { - this.maxKeysPerDay = maxKeysPerDay; + public void setConnectionPoolSize(final Integer connectionPoolSize) { + this.connectionPoolSize = connectionPoolSize; } - public static class Payload { - - @Min(7) - @Max(100) - private Integer maxNumberOfKeys; - @NotEmpty - private String[] supportedCountries; - private String defaultOriginCountry; - private Checkins checkins; - - public Integer getMaxNumberOfKeys() { - return maxNumberOfKeys; - } - - public void setMaxNumberOfKeys(Integer maxNumberOfKeys) { - this.maxNumberOfKeys = maxNumberOfKeys; - } - - public String[] getSupportedCountries() { - return supportedCountries; - } - - public void setSupportedCountries(String[] supportedCountries) { - this.supportedCountries = supportedCountries; - } - - public String getDefaultOriginCountry() { - return defaultOriginCountry; - } - - public void setDefaultOriginCountry(String defaultOriginCountry) { - this.defaultOriginCountry = defaultOriginCountry; - } - - public Checkins getCheckins() { - return checkins; - } - - public void setCheckins(Checkins checkins) { - this.checkins = checkins; - } - - public static class Checkins { - - @NotNull - private Integer acceptedEventDateThresholdDays; - - public Integer getAcceptedEventDateThresholdDays() { - return acceptedEventDateThresholdDays; - } - - public void setAcceptedEventDateThresholdDays(Integer acceptedEventDateThresholdDays) { - this.acceptedEventDateThresholdDays = acceptedEventDateThresholdDays; - } - } + public void setFakeDelayMovingAverageSamples(final Long fakeDelayMovingAverageSamples) { + this.fakeDelayMovingAverageSamples = fakeDelayMovingAverageSamples; } - public String getVerificationBaseUrl() { - return verification.getBaseUrl(); + public void setFeignRetry(final FeignRetry feignRetry) { + this.feignRetry = feignRetry; } - public void setVerification(Verification verification) { - this.verification = verification; + public void setInitialFakeDelayMilliseconds(final Long initialFakeDelayMilliseconds) { + this.initialFakeDelayMilliseconds = initialFakeDelayMilliseconds; } - public String getVerificationPath() { - return verification.getPath(); + public void setMaxAllowedCheckinsPerDay(final Integer maxAllowedCheckinsPerDay) { + this.maxAllowedCheckinsPerDay = maxAllowedCheckinsPerDay; } - static class Verification { - - @Pattern(regexp = URL_WITH_PORT_REGEX) - private String baseUrl; - - @Pattern(regexp = PATH_REGEX) - private String path; - - public String getBaseUrl() { - return baseUrl; - } - - public String getPath() { - return path; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - public void setPath(String path) { - this.path = path; - } + public void setMaximumRequestSize(final DataSize maximumRequestSize) { + this.maximumRequestSize = maximumRequestSize; } - private static class Monitoring { + public void setMaxKeysPerDay(final int maxKeysPerDay) { + this.maxKeysPerDay = maxKeysPerDay; + } - @Min(1) - @Max(1000) - private Long batchSize; + public void setMaxRollingPeriod(final Integer maxRollingPeriod) { + this.maxRollingPeriod = maxRollingPeriod; + } - public Long getBatchSize() { - return batchSize; - } + public void setMinRollingPeriod(final Integer minRollingPeriod) { + this.minRollingPeriod = minRollingPeriod; + } - public void setBatchSize(Long batchSize) { - this.batchSize = batchSize; - } + public void setMonitoring(final Monitoring monitoring) { + this.monitoring = monitoring; } - public Monitoring getMonitoring() { - return monitoring; + public void setMonitoringBatchSize(final Long batchSize) { + monitoring.setBatchSize(batchSize); } - public void setMonitoring(Monitoring monitoring) { - this.monitoring = monitoring; + public void setPayload(final Payload payload) { + this.payload = payload; } - public Long getMonitoringBatchSize() { - return this.monitoring.getBatchSize(); + public void setRandomCheckinsPaddingMultiplier(final Integer randomCheckinsPaddingMultiplier) { + this.randomCheckinsPaddingMultiplier = randomCheckinsPaddingMultiplier; } - public void setMonitoringBatchSize(Long batchSize) { - this.monitoring.setBatchSize(batchSize); + public void setRandomCheckinsPaddingPepper(final String randomCheckinsPaddingPepper) { + this.randomCheckinsPaddingPepper = randomCheckinsPaddingPepper; } - public Client getClient() { - return client; + public void setRandomKeyPaddingMultiplier(final Integer randomKeyPaddingMultiplier) { + this.randomKeyPaddingMultiplier = randomKeyPaddingMultiplier; } - public void setClient(Client client) { - this.client = client; + public void setRetentionDays(final Integer retentionDays) { + this.retentionDays = retentionDays; } - Verification getSrsVerify() { - return srsVerify; + public void setSrsDays(final int srsDays) { + this.srsDays = srsDays; } void setSrsVerify(final Verification srsVerify) { this.srsVerify = srsVerify; } - public String getSrsVerifyBaseUrl() { - return getSrsVerify().getBaseUrl(); + public void setSupportedCountries(final String[] supportedCountries) { + payload.setSupportedCountries(supportedCountries); } - public String getSrsVerifyPath() { - return getSrsVerify().getPath(); + public void setTekFieldDerivations(final TekFieldDerivations tekFieldDerivations) { + this.tekFieldDerivations = tekFieldDerivations; } - public static class Client { - - private Ssl ssl; - - public Ssl getSsl() { - return ssl; - } - - public void setSsl(Ssl ssl) { - this.ssl = ssl; - } - - public static class Ssl { - - private File keyStore; - private String keyStorePassword; - private String keyPassword; - private File trustStore; - private String trustStorePassword; - - public File getKeyStore() { - return keyStore; - } - - public void setKeyStore(File keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStorePassword() { - return keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getKeyPassword() { - return keyPassword; - } - - public void setKeyPassword(String keyPassword) { - this.keyPassword = keyPassword; - } - - public File getTrustStore() { - return trustStore; - } - - public void setTrustStore(File trustStore) { - this.trustStore = trustStore; - } + public void setTrlDerivations(final TrlDerivations trlDerivations) { + this.trlDerivations = trlDerivations; + } - public String getTrustStorePassword() { - return trustStorePassword; - } + public void setUnencryptedCheckinsEnabled(final Boolean unencryptedCheckinsEnabled) { + this.unencryptedCheckinsEnabled = unencryptedCheckinsEnabled; + } - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - } + public void setVerification(final Verification verification) { + this.verification = verification; } } diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java index 0e2da8cfb3..b1ac519a37 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/verification/CloudFeignClientProvider.java @@ -1,9 +1,13 @@ package app.coronawarn.server.services.submission.verification; +import static java.util.concurrent.TimeUnit.SECONDS; + import app.coronawarn.server.common.federation.client.hostname.HostnameVerifierProvider; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig; import app.coronawarn.server.services.submission.config.SubmissionServiceConfig.Client.Ssl; +import app.coronawarn.server.services.submission.config.SubmissionServiceConfig.FeignRetry; import feign.Client; +import feign.Retryer; import feign.httpclient.ApacheHttpClient; import java.io.File; import java.io.IOException; @@ -28,39 +32,36 @@ public class CloudFeignClientProvider { private final File trustStore; private final String trustStorePassword; + private final FeignRetry retry; + private final HostnameVerifierProvider hostnameVerifierProvider; /** * Creates a {@link CloudFeignClientProvider} that provides feign clients with fixed key and trust material. * - * @param config config attributes of {@link SubmissionServiceConfig} + * @param config config attributes of {@link SubmissionServiceConfig} * @param hostnameVerifierProvider provider {@link SubmissionServiceConfig} */ - public CloudFeignClientProvider(SubmissionServiceConfig config, HostnameVerifierProvider hostnameVerifierProvider) { - Ssl sslConfig = config.getClient().getSsl(); - this.keyStore = sslConfig.getKeyStore(); - this.keyStorePassword = sslConfig.getKeyStorePassword(); - this.keyPassword = sslConfig.getKeyPassword(); - this.trustStore = sslConfig.getTrustStore(); - this.trustStorePassword = sslConfig.getTrustStorePassword(); - this.connectionPoolSize = config.getConnectionPoolSize(); + public CloudFeignClientProvider(final SubmissionServiceConfig config, + final HostnameVerifierProvider hostnameVerifierProvider) { + final Ssl sslConfig = config.getClient().getSsl(); + keyStore = sslConfig.getKeyStore(); + keyStorePassword = sslConfig.getKeyStorePassword(); + keyPassword = sslConfig.getKeyPassword(); + trustStore = sslConfig.getTrustStore(); + trustStorePassword = sslConfig.getTrustStorePassword(); + connectionPoolSize = config.getConnectionPoolSize(); this.hostnameVerifierProvider = hostnameVerifierProvider; + retry = config.getFeignRetry(); } - public Client createFeignClient() { - return new ApacheHttpClient(createHttpClientFactory().createBuilder().build()); + @Bean + public ApacheHttpClientConnectionManagerFactory createConnectionManager() { + return new DefaultApacheHttpClientConnectionManagerFactory(); } - private SSLContext getSslContext() { - try { - return SSLContextBuilder - .create() - .loadKeyMaterial(this.keyStore, this.keyStorePassword.toCharArray(), this.keyPassword.toCharArray()) - .loadTrustMaterial(this.trustStore, this.trustStorePassword.toCharArray()) - .build(); - } catch (IOException | GeneralSecurityException e) { - throw new RuntimeException(e); - } + public Client createFeignClient() { + return new ApacheHttpClient(createHttpClientFactory().createBuilder().build()); } /** @@ -71,14 +72,31 @@ private SSLContext getSslContext() { @Bean public ApacheHttpClientFactory createHttpClientFactory() { return new DefaultApacheHttpClientFactory(HttpClientBuilder.create() - .setMaxConnPerRoute(this.connectionPoolSize) - .setMaxConnTotal(this.connectionPoolSize) + .setMaxConnPerRoute(connectionPoolSize) + .setMaxConnTotal(connectionPoolSize) .setSSLContext(getSslContext()) .setSSLHostnameVerifier(hostnameVerifierProvider.createHostnameVerifier())); } + private SSLContext getSslContext() { + try { + return SSLContextBuilder + .create() + .loadKeyMaterial(keyStore, keyStorePassword.toCharArray(), keyPassword.toCharArray()) + .loadTrustMaterial(trustStore, trustStorePassword.toCharArray()) + .build(); + } catch (final IOException | GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + /** + * Creates new {@link Retryer} with {@link #retry} values. + * + * @return {@link Retryer} with {@link #retry} values. + */ @Bean - public ApacheHttpClientConnectionManagerFactory createConnectionManager() { - return new DefaultApacheHttpClientConnectionManagerFactory(); + public Retryer retryer() { + return new Retryer.Default(retry.getPeriod(), SECONDS.toMillis(retry.getMaxPeriod()), retry.getMaxAttempts()); } } diff --git a/services/submission/src/main/resources/application.yaml b/services/submission/src/main/resources/application.yaml index fa0a960af1..4515aafa4c 100644 --- a/services/submission/src/main/resources/application.yaml +++ b/services/submission/src/main/resources/application.yaml @@ -20,7 +20,7 @@ services: fake-delay-moving-average-samples: 10 # The retention threshold for acceptable diagnosis keys during submission. retention-days: 14 - # How many days in the past are keys of a self reported submission allowed. + # How many days in the past are keys of a self reported submission allowed. Only if it's smaller then 'retention-days' it will have an effect. srs-days: ${MAX_NUMBER_OF_DAYS_FOR_SRS:14} # The number of keys to save to the DB for every real submitted key. # Example: If the 'random-key-padding-multiplier' is set to 10, and 5 keys are being submitted, @@ -75,6 +75,13 @@ services: key-store-password: ${SSL_SUBMISSION_KEYSTORE_PASSWORD} trust-store: ${SSL_VERIFICATION_TRUSTSTORE_PATH} trust-store-password: ${SSL_VERIFICATION_TRUSTSTORE_PASSWORD} + feign-retry: + # feign retry waiting time in milliseconds + period: ${FEIGN_RETRY_PERIOD:500} + # feign total maximum wait time in seconds before feign.RetryableException + max-period: ${FEIGN_RETRY_MAX_PERIOD:10} + # feign max retry attemps, when reached feign.RetryableException is thrown + max-attempts: ${FEIGN_RETRY_MAX_ATTEMPTS:10} spring: lifecycle: diff --git a/services/submission/src/test/resources/application.yaml b/services/submission/src/test/resources/application.yaml index 594c8e7602..b3ffffb273 100644 --- a/services/submission/src/test/resources/application.yaml +++ b/services/submission/src/test/resources/application.yaml @@ -60,6 +60,13 @@ services: key-store-password: 123456 trust-store: ../../docker-compose-test-secrets/contains_efgs_truststore.jks trust-store-password: 123456 + feign-retry: + # feign retry waiting time in milliseconds + period: ${FEIGN_RETRY_PERIOD:500} + # feign total maximum wait time in seconds before feign.RetryableException + max-period: ${FEIGN_RETRY_MAX_PERIOD:10} + # feign max retry attemps, when reached feign.RetryableException is thrown + max-attempts: ${FEIGN_RETRY_MAX_ATTEMPTS:10} management: endpoint: From c998852a775693c9a5dde33135af8965cd1bb5fa Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 15:52:03 +0100 Subject: [PATCH 79/83] checkstyle --- .../services/submission/config/SubmissionServiceConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3438c0473c..83655e6769 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 @@ -227,7 +227,8 @@ public void setPath(final String path) { private static final String PATH_REGEX = "^[/]?[a-zA-Z0-9_]{1,1024}(/[a-zA-Z0-9_]{1,1024}){0,256}[/]?$"; - private static final String URL_WITH_PORT_REGEX = "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; + private static final String URL_WITH_PORT_REGEX + = "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; private Client client; From e9d33570cffe8d0a8942ccd0ce5ef4cec5d094a2 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 15:53:07 +0100 Subject: [PATCH 80/83] indent --- .../services/submission/config/SubmissionServiceConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 83655e6769..f1528e2276 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 @@ -228,7 +228,7 @@ public void setPath(final String path) { private static final String PATH_REGEX = "^[/]?[a-zA-Z0-9_]{1,1024}(/[a-zA-Z0-9_]{1,1024}){0,256}[/]?$"; private static final String URL_WITH_PORT_REGEX - = "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; + = "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; private Client client; From 05eda53e3d94983cbe820d8cab580014688c4dc6 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Fri, 16 Dec 2022 17:00:12 +0100 Subject: [PATCH 81/83] sonar code smell --- .../services/submission/config/SubmissionServiceConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f1528e2276..29a887b64e 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 @@ -228,7 +228,7 @@ public void setPath(final String path) { private static final String PATH_REGEX = "^[/]?[a-zA-Z0-9_]{1,1024}(/[a-zA-Z0-9_]{1,1024}){0,256}[/]?$"; private static final String URL_WITH_PORT_REGEX - = "^http[s]?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:[0-9]{2,6})?$"; + = "^https?://[a-z0-9-]{1,1024}(\\.[a-z0-9-]{1,1024}){0,256}(:\\d{2,6})?$"; private Client client; From 468bf57472a0f941265706d39646b5ada793ac62 Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 19 Dec 2022 13:47:35 +0100 Subject: [PATCH 82/83] move KEYS_ALREADY_EXIST check, before OTP redemption --- .../resources/db/migration/V26__addIndex.sql | 8 ++++ .../DiagnosisKeyExistsAlreadyException.java | 5 --- .../controller/SubmissionController.java | 39 ++++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 common/persistence/src/main/resources/db/migration/V26__addIndex.sql delete mode 100644 services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java diff --git a/common/persistence/src/main/resources/db/migration/V26__addIndex.sql b/common/persistence/src/main/resources/db/migration/V26__addIndex.sql new file mode 100644 index 0000000000..2bf6d5d853 --- /dev/null +++ b/common/persistence/src/main/resources/db/migration/V26__addIndex.sql @@ -0,0 +1,8 @@ +/* Submission Controller - diagnosisKeyService.exists(...) */ +CREATE INDEX IF NOT EXISTS key_data_idx ON diagnosis_key (key_data); + +/* DiagnosisKeysStructureProvider - diagnosisKeyService.getDiagnosisKeysWithMinTrl(...) */ +CREATE INDEX IF NOT EXISTS trl_and_time_idx ON diagnosis_key (transmission_risk_level, submission_timestamp); + +/* Distribution */ +CREATE INDEX IF NOT EXISTS submission_time_idx ON diagnosis_key (submission_timestamp); diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java deleted file mode 100644 index bcb65e3aa9..0000000000 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/DiagnosisKeyExistsAlreadyException.java +++ /dev/null @@ -1,5 +0,0 @@ -package app.coronawarn.server.services.submission.controller; - -final class DiagnosisKeyExistsAlreadyException extends Exception { - private static final long serialVersionUID = 6692058450561715150L; -} diff --git a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java index 25b0c4f462..80254e4fd9 100644 --- a/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java +++ b/services/submission/src/main/java/app/coronawarn/server/services/submission/controller/SubmissionController.java @@ -241,12 +241,22 @@ private DeferredResult> buildRealDeferredResult(final Submi StopWatch stopWatch = new StopWatch(); stopWatch.start(); try { + final BodyBuilder response = ResponseEntity.ok(); + final Collection diagnosisKeys = extractValidDiagnosisKeysFromPayload( + enhanceWithDefaultValuesIfMissing(payload), response); + + if (isSelfReport(payload) && diagnosisKeyService.exists(diagnosisKeys)) { + logger.warn(SECURITY, "Self-Report contains already persisted keys - {}", + new PrintableSubmissionPayload(payload)); + deferredResult + .setResult(ResponseEntity.status(BAD_REQUEST).header("cwa-error-code", "KEYS_ALREADY_EXIST").build()); + } + if (!tanVerifier.verifyTan(tan)) { submissionMonitor.incrementInvalidTanRequestCounter(); deferredResult.setResult(ResponseEntity.status(FORBIDDEN).build()); } else { - final BodyBuilder response = ResponseEntity.ok(); - extractAndStoreDiagnosisKeys(payload, response); + saveDiagnosisKeys(diagnosisKeys); CheckinsStorageResult checkinsStorageResult = eventCheckinFacade.extractAndStoreCheckins(payload); @@ -264,11 +274,6 @@ private DeferredResult> buildRealDeferredResult(final Submi } catch (final FeignException e) { logger.error("Verification Service could not be reached.", e); deferredResult.setErrorResult(e); - } catch (final DiagnosisKeyExistsAlreadyException e) { - logger.warn(SECURITY, "Self-Report contains already persisted keys - {}", - new PrintableSubmissionPayload(payload)); - deferredResult - .setResult(ResponseEntity.status(BAD_REQUEST).header("cwa-error-code", "KEYS_ALREADY_EXIST").build()); } catch (final Exception e) { logger.error(e.getLocalizedMessage(), e); deferredResult.setErrorResult(e); @@ -279,26 +284,14 @@ private DeferredResult> buildRealDeferredResult(final Submi return deferredResult; } - private void extractAndStoreDiagnosisKeys(final SubmissionPayload submissionPayload, final BodyBuilder response) - throws DiagnosisKeyExistsAlreadyException { - final Collection diagnosisKeys = extractValidDiagnosisKeysFromPayload( - enhanceWithDefaultValuesIfMissing(submissionPayload), response); - - if (isSelfReport(submissionPayload) && diagnosisKeyService.exists(diagnosisKeys)) { - throw new DiagnosisKeyExistsAlreadyException(); - } - - for (final DiagnosisKey diagnosisKey : diagnosisKeys) { - mapTrasmissionRiskValue(diagnosisKey); + private void saveDiagnosisKeys(final Collection diagnosisKeys) { + for (final DiagnosisKey key : diagnosisKeys) { + // TRL mapping + key.setTransmissionRiskLevel(trlDerivations.mapFromTrlSubmittedToTrlToStore(key.getTransmissionRiskLevel())); } diagnosisKeyService.saveDiagnosisKeys(padDiagnosisKeys(diagnosisKeys)); } - private void mapTrasmissionRiskValue(DiagnosisKey diagnosisKey) { - diagnosisKey.setTransmissionRiskLevel( - trlDerivations.mapFromTrlSubmittedToTrlToStore(diagnosisKey.getTransmissionRiskLevel())); - } - private Collection extractValidDiagnosisKeysFromPayload(final SubmissionPayload submissionPayload, final BodyBuilder response) { final Collection protoBufferKeys = submissionPayload.getKeysList(); From 371a144c0c52ad0ad088737e58952f93349a629d Mon Sep 17 00:00:00 2001 From: Hilmar Falkenberg Date: Mon, 19 Dec 2022 13:48:06 +0100 Subject: [PATCH 83/83] quick launch --- runconfigs/Eclipse/spring-boot-submission.launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runconfigs/Eclipse/spring-boot-submission.launch b/runconfigs/Eclipse/spring-boot-submission.launch index 134c1b2fb7..4ac5918d12 100644 --- a/runconfigs/Eclipse/spring-boot-submission.launch +++ b/runconfigs/Eclipse/spring-boot-submission.launch @@ -2,7 +2,7 @@ - +