├── scripts ├── .gitignore ├── domainnames.conf ├── private.pem ├── generate_OTPs.sh ├── certificate.crt └── DpkgHelper.java ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 05_other.md │ ├── 04_question.md │ ├── 02_feature_request.md │ ├── 03_enhancement.md │ └── 01_bug.md ├── dependabot.yml └── workflows │ ├── license-analysis.yml │ ├── markdown-analysis.yml │ └── codeql-analysis.yml ├── .gitattributes ├── secrets ├── ssl.p12 ├── client_cert.p12 ├── truststore.jks ├── cwa_data_ppac.jks └── README.md ├── services ├── edus │ └── src │ │ ├── main │ │ ├── resources │ │ │ ├── bootstrap.yaml │ │ │ ├── application-debug.yaml │ │ │ ├── application-disable-ssl-client-postgres.yaml │ │ │ ├── application-cloud.yaml │ │ │ └── bootstrap-cloud.yaml │ │ └── java │ │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── services │ │ │ └── edus │ │ │ └── otp │ │ │ ├── OtpRedemptionRequest.java │ │ │ ├── OtpState.java │ │ │ ├── OtpRedemptionResponse.java │ │ │ └── OtpControllerExceptionHandler.java │ │ └── test │ │ ├── resources │ │ ├── bootstrap.yaml │ │ └── application.yaml │ │ └── java │ │ └── app │ │ └── coronawarn │ │ └── datadonation │ │ └── services │ │ └── edus │ │ └── otp │ │ └── StringUtils.java ├── ppac │ └── src │ │ ├── main │ │ ├── resources │ │ │ ├── bootstrap.yaml │ │ │ ├── application-debug.yaml │ │ │ ├── application-disable-ssl-client-postgres.yaml │ │ │ ├── application-cloud.yaml │ │ │ ├── bootstrap-cloud.yaml │ │ │ └── certificates │ │ │ │ └── test.cert │ │ └── java │ │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── services │ │ │ └── ppac │ │ │ ├── android │ │ │ ├── attestation │ │ │ │ ├── salt │ │ │ │ │ ├── SaltVerificationStrategy.java │ │ │ │ │ └── LoadTestSaltVerificationStrategy.java │ │ │ │ ├── AndroidIdVerificationStrategy.java │ │ │ │ ├── timestamp │ │ │ │ │ ├── TimestampVerificationStrategy.java │ │ │ │ │ ├── NoOpTimestampVerificationStrategy.java │ │ │ │ │ └── ProdTimestampVerificationStrategy.java │ │ │ │ ├── SrsRateLimitVerificationStrategy.java │ │ │ │ ├── errors │ │ │ │ │ ├── AndroidIdNotValid.java │ │ │ │ │ ├── FailedJwsParsing.java │ │ │ │ │ ├── CtsProfileMatchRequired.java │ │ │ │ │ ├── MissingMandatoryAuthenticationFields.java │ │ │ │ │ ├── BasicIntegrityIsRequired.java │ │ │ │ │ ├── ApkPackageNameNotAllowed.java │ │ │ │ │ ├── FailedAttestationHostnameValidation.java │ │ │ │ │ ├── FailedAttestationTimestampValidation.java │ │ │ │ │ ├── BasicEvaluationTypeNotPresent.java │ │ │ │ │ ├── ApkCertificateDigestsNotAllowed.java │ │ │ │ │ ├── HardwareBackedEvaluationTypeNotPresent.java │ │ │ │ │ ├── NonceCalculationError.java │ │ │ │ │ ├── NonceCouldNotBeVerified.java │ │ │ │ │ ├── SaltNotValidAnymore.java │ │ │ │ │ ├── FailedSignatureVerification.java │ │ │ │ │ ├── DeviceQuotaExceeded.java │ │ │ │ │ └── AndroidIdUpsertError.java │ │ │ │ └── signature │ │ │ │ │ ├── SignatureVerificationStrategy.java │ │ │ │ │ ├── ProdSignatureVerificationStrategy.java │ │ │ │ │ └── TestSignatureVerificationStrategy.java │ │ │ ├── config │ │ │ │ └── ThirdPartyBeanRegistration.java │ │ │ └── controller │ │ │ │ ├── AndroidDelayManager.java │ │ │ │ ├── validation │ │ │ │ ├── ElsOneTimePasswordRequestAndroidValidator.java │ │ │ │ ├── SrsOneTimePasswordRequestAndroidValidator.java │ │ │ │ ├── EdusOneTimePasswordRequestAndroidValidator.java │ │ │ │ └── ValidAndroidOneTimePasswordRequest.java │ │ │ │ ├── TestProfileExceptionHandler.java │ │ │ │ └── DeleteSaltController.java │ │ │ ├── ios │ │ │ ├── verification │ │ │ │ ├── errors │ │ │ │ │ ├── DeviceBlocked.java │ │ │ │ │ ├── ApiTokenExpired.java │ │ │ │ │ ├── DeviceTokenInvalid.java │ │ │ │ │ ├── ApiTokenQuotaExceeded.java │ │ │ │ │ ├── DeviceTokenRedeemed.java │ │ │ │ │ ├── DeviceTokenSyntaxError.java │ │ │ │ │ ├── ApiTokenAlreadyUsed.java │ │ │ │ │ ├── ExternalServiceError.java │ │ │ │ │ └── InternalServerError.java │ │ │ │ ├── devicetoken │ │ │ │ │ ├── DeviceTokenRedemptionStrategy.java │ │ │ │ │ ├── LoadTestDeviceTokenRedemptionStrategy.java │ │ │ │ │ └── ProdDeviceTokenRedemptionStrategy.java │ │ │ │ ├── apitoken │ │ │ │ │ ├── authentication │ │ │ │ │ │ ├── LoadTestApiTokenAuthenticationStrategy.java │ │ │ │ │ │ ├── ProdApiTokenAuthenticationStrategy.java │ │ │ │ │ │ ├── TestApiTokenAuthenticationStrategy.java │ │ │ │ │ │ └── ApiTokenAuthenticationStrategy.java │ │ │ │ │ ├── ApiTokenBuilder.java │ │ │ │ │ └── LoadTestApiTokenService.java │ │ │ │ ├── scenario │ │ │ │ │ └── ratelimit │ │ │ │ │ │ ├── PpacIosRateLimitStrategy.java │ │ │ │ │ │ └── LoadTestPpacIosRateLimitStrategy.java │ │ │ │ └── devicedata │ │ │ │ │ └── LoadtestPerDeviceDataValidator.java │ │ │ ├── controller │ │ │ │ ├── IosDelayManager.java │ │ │ │ └── validation │ │ │ │ │ ├── ElsOneTimePasswordRequestIosValidator.java │ │ │ │ │ ├── SrsOneTimePasswordRequestIosValidator.java │ │ │ │ │ ├── EdusOneTimePasswordRequestIosValidator.java │ │ │ │ │ ├── ValidPpaDataRequestIosPayload.java │ │ │ │ │ └── ValidOneTimePasswordRequestIos.java │ │ │ └── client │ │ │ │ ├── IosDeviceApiClient.java │ │ │ │ └── domain │ │ │ │ ├── PerDeviceDataResponse.java │ │ │ │ ├── PerDeviceValidationRequest.java │ │ │ │ └── PerDeviceDataQueryRequest.java │ │ │ ├── commons │ │ │ ├── PpaDataRequestValidationFailed.java │ │ │ ├── PpaDataRequestValidator.java │ │ │ ├── web │ │ │ │ └── DataSubmissionResponse.java │ │ │ ├── validation │ │ │ │ └── UuidConstraintValidator.java │ │ │ └── AbstractDelayManager.java │ │ │ └── logging │ │ │ └── PpacLogger.java │ │ └── test │ │ ├── java │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── services │ │ │ └── ppac │ │ │ ├── config │ │ │ ├── TestBeanConfig.java │ │ │ └── AndroidTestBeanConfig.java │ │ │ ├── android │ │ │ ├── config │ │ │ │ └── AndroidServiceConfigTest.java │ │ │ └── attestation │ │ │ │ └── TimeUtilsTest.java │ │ │ ├── commons │ │ │ └── AbstractDelayManagerTest.java │ │ │ └── ios │ │ │ ├── apitoken │ │ │ └── ApiTokenDataBuilderTest.java │ │ │ └── client │ │ │ └── domain │ │ │ └── PerDeviceDataResponseTest.java │ │ └── resources │ │ └── certificates │ │ └── test.cert ├── els-verify │ └── src │ │ ├── main │ │ ├── resources │ │ │ ├── bootstrap.yaml │ │ │ ├── application-debug.yaml │ │ │ ├── application-disable-ssl-client-postgres.yaml │ │ │ ├── application-cloud.yaml │ │ │ └── bootstrap-cloud.yaml │ │ └── java │ │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── services │ │ │ └── els │ │ │ └── otp │ │ │ ├── ElsOtpRedemptionRequest.java │ │ │ ├── ElsOtpRedemptionResponse.java │ │ │ └── ElsOtpControllerExceptionHandler.java │ │ └── test │ │ ├── resources │ │ ├── bootstrap.yaml │ │ └── application.yaml │ │ └── java │ │ └── app │ │ └── coronawarn │ │ └── datadonation │ │ └── services │ │ └── els │ │ └── otp │ │ ├── StringUtils.java │ │ └── ElsOtpRedemtionResponseTest.java ├── retention │ └── src │ │ ├── main │ │ └── resources │ │ │ ├── bootstrap.yaml │ │ │ ├── application-debug.yaml │ │ │ ├── application-disable-ssl-client-postgres.yaml │ │ │ ├── application-cloud.yaml │ │ │ └── bootstrap-cloud.yaml │ │ └── test │ │ └── resources │ │ └── application.yaml └── srs-verify │ └── src │ ├── main │ ├── resources │ │ ├── bootstrap.yaml │ │ ├── application-debug.yaml │ │ ├── application-disable-ssl-client-postgres.yaml │ │ ├── application-cloud.yaml │ │ └── bootstrap-cloud.yaml │ └── java │ │ └── app │ │ └── coronawarn │ │ └── datadonation │ │ └── services │ │ └── srs │ │ └── otp │ │ ├── SrsOtpRedemptionRequest.java │ │ └── SrsOtpControllerExceptionHandler.java │ └── test │ ├── resources │ ├── bootstrap.yaml │ └── application.yaml │ └── java │ └── app │ └── coronawarn │ └── datadonation │ └── services │ └── srs │ └── otp │ ├── SrsOtpControllerTest.java │ ├── StringUtils.java │ └── SrsOtpRedemtionResponseTest.java ├── .mvn ├── maven.config └── wrapper │ └── maven-wrapper.properties ├── common ├── persistence │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── db │ │ │ │ ├── migration │ │ │ │ ├── V11__alterScanInstanceId.sql │ │ │ │ ├── V13__alterScanInstanceAddDate.sql │ │ │ │ ├── V17__alterTableExposureWindowAtTestRegistration.sql │ │ │ │ ├── V16__alterTablesWithAndroidEnfVersion.sql │ │ │ │ ├── V23__alterMetadataTables.sql │ │ │ │ ├── V10__alterScanInstanceTable.sql │ │ │ │ ├── V19__alterTableScanInstanceAtTestRegistration.sql │ │ │ │ ├── V3__alterTables.sql │ │ │ │ ├── V5__createLogTables.sql │ │ │ │ ├── V21__featureSelfReportSubmissions.sql │ │ │ │ ├── V9__alterMetadataTables.sql │ │ │ │ ├── V20__alterFKtoBigInt.sql │ │ │ │ ├── V18__alterTablesWithTechnicalMetadata.sql │ │ │ │ └── V12__alterSerialIds.sql │ │ │ │ └── specific │ │ │ │ └── postgresql │ │ │ │ ├── V7__grantElsVerifyPermissions.sql │ │ │ │ ├── V6__grantLogPerimissions.sql │ │ │ │ ├── V22__grantSRSPerimissions.sql │ │ │ │ ├── V15__grantPermissionForNewTablesPPAExtention.sql │ │ │ │ └── V4__grantPermissions.sql │ │ └── java │ │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── common │ │ │ ├── persistence │ │ │ ├── service │ │ │ │ ├── OtpStatusException.java │ │ │ │ ├── OtpNotFoundException.java │ │ │ │ ├── DeleteSaltException.java │ │ │ │ ├── OtpState.java │ │ │ │ ├── OtpService.java │ │ │ │ ├── ElsOtpService.java │ │ │ │ ├── SrsOtpService.java │ │ │ │ ├── SaltService.java │ │ │ │ ├── OtpTestGenerationResponse.java │ │ │ │ └── OtpCreationResponse.java │ │ │ ├── errors │ │ │ │ └── MetricsDataCouldNotBeStored.java │ │ │ ├── domain │ │ │ │ ├── ElsOneTimePassword.java │ │ │ │ ├── SrsOneTimePassword.java │ │ │ │ ├── ppac │ │ │ │ │ └── android │ │ │ │ │ │ └── SaltData.java │ │ │ │ ├── DeviceToken.java │ │ │ │ ├── AndroidId.java │ │ │ │ └── metrics │ │ │ │ │ └── DataDonationMetric.java │ │ │ └── repository │ │ │ │ ├── metrics │ │ │ │ ├── ScanInstanceRepository.java │ │ │ │ ├── UserMetadataRepository.java │ │ │ │ ├── ClientMetadataRepository.java │ │ │ │ ├── ExposureWindowRepository.java │ │ │ │ ├── TestResultMetadataRepository.java │ │ │ │ ├── ExposureRiskMetadataRepository.java │ │ │ │ ├── ExposureWindowTestResultsRepository.java │ │ │ │ ├── ScanInstancesAtTestRegistrationRepository.java │ │ │ │ ├── ExposureWindowsAtTestRegistrationRepository.java │ │ │ │ ├── KeySubmissionMetadataWithUserMetadataRepository.java │ │ │ │ ├── KeySubmissionMetadataWithClientMetadataRepository.java │ │ │ │ └── SummarizedExposureWindowsWithUserMetadataRepository.java │ │ │ │ ├── ppac │ │ │ │ └── android │ │ │ │ │ └── SaltRepository.java │ │ │ │ ├── DeviceTokenRepository.java │ │ │ │ ├── OneTimePasswordRepository.java │ │ │ │ ├── SrsOneTimePasswordRepository.java │ │ │ │ ├── ElsOneTimePasswordRepository.java │ │ │ │ └── AndroidIdRepository.java │ │ │ ├── config │ │ │ └── SecurityLogger.java │ │ │ └── validation │ │ │ ├── DateInRangeValidator.java │ │ │ └── DateInRange.java │ │ └── test │ │ ├── java │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── common │ │ │ ├── TestApplication.java │ │ │ ├── persistence │ │ │ ├── service │ │ │ │ ├── OtpStateTest.java │ │ │ │ └── SaltServiceTest.java │ │ │ ├── repository │ │ │ │ ├── android │ │ │ │ │ └── SaltDataRepositoryTest.java │ │ │ │ └── metrics │ │ │ │ │ └── UserMetadataRepositoryTest.java │ │ │ └── domain │ │ │ │ └── metrics │ │ │ │ ├── TechnicalMetadataTest.java │ │ │ │ └── ScanInstanceTest.java │ │ │ └── validation │ │ │ └── ObjectWithDateMember.java │ │ ├── resources │ │ └── application.yml │ │ └── assembly.xml ├── protocols │ ├── src │ │ └── main │ │ │ └── proto │ │ │ └── app │ │ │ └── coronawarn │ │ │ └── datadonation │ │ │ └── common │ │ │ └── protocols │ │ │ ├── internal │ │ │ └── ppdd │ │ │ │ ├── edus_otp.proto │ │ │ │ ├── els_otp.proto │ │ │ │ ├── srs_otp.proto │ │ │ │ ├── tri_state_boolean.proto │ │ │ │ ├── ppac_android.proto │ │ │ │ ├── ppac_ios.proto │ │ │ │ ├── edus_otp_request_ios.proto │ │ │ │ ├── els_otp_request_ios.proto │ │ │ │ ├── els_otp_request_android.proto │ │ │ │ ├── edus_otp_request_android.proto │ │ │ │ ├── srs_otp_request_ios.proto │ │ │ │ ├── ppa_data_request_ios.proto │ │ │ │ ├── ppa_data_request_android.proto │ │ │ │ └── srs_otp_request_android.proto │ │ │ └── auth.proto │ └── pom.xml └── pom.xml ├── codestyle ├── .markdownlint.yml └── .markdown-link-check.json ├── docs └── ARCHITECTURE.md ├── .env ├── .reuse └── dep5 ├── setup ├── create-users.sql ├── create-analytics-user.sql └── setup-roles.sql ├── CODEOWNERS ├── .gitignore └── log4j2 ├── default ├── log4j2.xml └── log4j2-dev.xml └── test └── log4j2.xml /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | keys/ 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | maven-wrapper.properties text eol=lf 2 | mvnw text eol=lf 3 | -------------------------------------------------------------------------------- /secrets/ssl.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corona-warn-app/cwa-ppa-server/HEAD/secrets/ssl.p12 -------------------------------------------------------------------------------- /secrets/client_cert.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corona-warn-app/cwa-ppa-server/HEAD/secrets/client_cert.p12 -------------------------------------------------------------------------------- /secrets/truststore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corona-warn-app/cwa-ppa-server/HEAD/secrets/truststore.jks -------------------------------------------------------------------------------- /secrets/cwa_data_ppac.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corona-warn-app/cwa-ppa-server/HEAD/secrets/cwa_data_ppac.jks -------------------------------------------------------------------------------- /services/edus/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/edus/src/test/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/ppac/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/els-verify/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/els-verify/src/test/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/retention/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/srs-verify/src/test/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /services/edus/src/main/resources/application-debug.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org: 4 | springframework: DEBUG 5 | root: DEBUG 6 | -------------------------------------------------------------------------------- /services/ppac/src/main/resources/application-debug.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org: 4 | springframework: DEBUG 5 | root: DEBUG 6 | -------------------------------------------------------------------------------- /services/els-verify/src/main/resources/application-debug.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org: 4 | springframework: DEBUG 5 | root: DEBUG 6 | -------------------------------------------------------------------------------- /services/retention/src/main/resources/application-debug.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org: 4 | springframework: DEBUG 5 | root: DEBUG 6 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/resources/application-debug.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org: 4 | springframework: DEBUG 5 | root: DEBUG 6 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Drevision=3.3.0-SNAPSHOT 2 | -Dlicense.projectName=Corona-Warn-App 3 | -Dlicense.inceptionYear=2020 4 | -Dlicense.licenseName=apache_v2 5 | -------------------------------------------------------------------------------- /scripts/domainnames.conf: -------------------------------------------------------------------------------- 1 | subjectAltName = @alt_names 2 | 3 | [ alt_names ] 4 | DNS.0 = localhost 5 | DNS.1 = ppac 6 | DNS.2 = edus 7 | DNS.3 = els-verify 8 | DNS.4 = srs-verify 9 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V11__alterScanInstanceId.sql: -------------------------------------------------------------------------------- 1 | ALTER SEQUENCE scan_instance_id_seq AS bigint; 2 | 3 | ALTER TABLE scan_instance ALTER COLUMN id TYPE bigint; 4 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/specific/postgresql/V7__grantElsVerifyPermissions.sql: -------------------------------------------------------------------------------- 1 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.els_one_time_password TO cwa_ppdd_els_verify; 2 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V13__alterScanInstanceAddDate.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE scan_instance DROP CONSTRAINT fk_exposure_window_id; 2 | ALTER TABLE scan_instance ADD COLUMN submitted_at DATE; 3 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V17__alterTableExposureWindowAtTestRegistration.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE exposure_windows_at_test_registration 2 | ADD COLUMN submitted_at DATE NOT NULL DEFAULT CURRENT_DATE; 3 | -------------------------------------------------------------------------------- /codestyle/.markdownlint.yml: -------------------------------------------------------------------------------- 1 | default: true 2 | 3 | MD013: false #https://github.com/DavidAnson/markdownlint/blob/HEAD/doc/Rules.md#md013 4 | MD033: false #https://github.com/DavidAnson/markdownlint/blob/HEAD/doc/Rules.md#md033 5 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Data Donation Server Architecture 2 | 3 | Will follow soon. 4 | 5 | ## Services 6 | 7 | - [EDUS Service](./EDUS.md) 8 | - [PPAC Service](./PPAC.md) 9 | - [Retention Service](./RETENTION.md) 10 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V16__alterTablesWithAndroidEnfVersion.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE exposure_window_test_result 2 | ALTER COLUMN android_api_level TYPE BIGINT, 3 | ALTER COLUMN android_enf_version TYPE BIGINT; 4 | -------------------------------------------------------------------------------- /services/edus/src/main/resources/application-disable-ssl-client-postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | datasource: 4 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST:localhost}:${POSTGRESQL_SERVICE_PORT:5432}/${POSTGRESQL_DATABASE:cwa-ppdd} 5 | -------------------------------------------------------------------------------- /services/ppac/src/main/resources/application-disable-ssl-client-postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | datasource: 4 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST:localhost}:${POSTGRESQL_SERVICE_PORT:5432}/${POSTGRESQL_DATABASE:cwa-ppdd} 5 | -------------------------------------------------------------------------------- /services/retention/src/main/resources/application-disable-ssl-client-postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | datasource: 4 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST:localhost}:${POSTGRESQL_SERVICE_PORT:5432}/${POSTGRESQL_DATABASE:cwa} 5 | -------------------------------------------------------------------------------- /services/els-verify/src/main/resources/application-disable-ssl-client-postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | datasource: 4 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST:localhost}:${POSTGRESQL_SERVICE_PORT:5432}/${POSTGRESQL_DATABASE:cwa-ppdd} 5 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/resources/application-disable-ssl-client-postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | datasource: 4 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST:localhost}:${POSTGRESQL_SERVICE_PORT:5432}/${POSTGRESQL_DATABASE:cwa-ppdd} 5 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /scripts/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEINOQcM6jChvjAwf0B3C2ex7Ronsc2VH4qIZK91n1/tgJoAoGCCqGSM49 3 | AwEHoUQDQgAE9/HUs+ssvOdmv+BZPjubaUiYOWYTd5iRMopbdBzpEPXbyQBSmOFe 4 | sVJ7y3GTU/1ql9FuIrqB7YBkhZZExPEqEw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V23__alterMetadataTables.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE key_submission_metadata_with_user_metadata ADD COLUMN IF NOT EXISTS submission_type SMALLINT; 2 | 3 | ALTER TABLE key_submission_metadata_with_client_metadata ADD COLUMN IF NOT EXISTS submission_type SMALLINT; 4 | -------------------------------------------------------------------------------- /secrets/README.md: -------------------------------------------------------------------------------- 1 | About the secrets contained here for development and testing 2 | ============================================================= 3 | 4 | `cwa_data_ppac.jks`: The secret used to run data and edus services on CWA-Backend side. 5 | 6 | The password for the `cwa_data_ppac.jks` file is: `123456`. 7 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/salt/SaltVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.salt; 2 | 3 | public interface SaltVerificationStrategy { 4 | 5 | void validateSalt(String saltString); 6 | } 7 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/AndroidIdVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation; 2 | 3 | public interface AndroidIdVerificationStrategy { 4 | 5 | void validateAndroidId(final byte[] androidId); 6 | } 7 | -------------------------------------------------------------------------------- /codestyle/.markdown-link-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "replacementPatterns": [ 3 | { 4 | "pattern": "^/", 5 | "replacement": "/github/workspace/" 6 | } 7 | ], 8 | "ignorePatterns": [ 9 | { 10 | "pattern": "^http[s]*://localhost.*" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/timestamp/TimestampVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.timestamp; 2 | 3 | public interface TimestampVerificationStrategy { 4 | 5 | void validateTimestamp(long attestationTimestamp); 6 | } 7 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/SrsRateLimitVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation; 2 | 3 | public interface SrsRateLimitVerificationStrategy { 4 | 5 | void validateSrsRateLimit(final byte[] androidId, final boolean acceptAndroidId); 6 | } 7 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/edus_otp.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | 6 | message EDUSOneTimePassword { 7 | 8 | string otp = 1; 9 | } -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/els_otp.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | 6 | message ELSOneTimePassword { 7 | 8 | string otp = 1; 9 | } -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/srs_otp.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | 6 | message SRSOneTimePassword { 7 | 8 | string otp = 1; 9 | } -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/specific/postgresql/V6__grantLogPerimissions.sql: -------------------------------------------------------------------------------- 1 | 2 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.els_one_time_password TO cwa_ppdd_ppac; 3 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.els_one_time_password TO cwa_ppdd_edus; 4 | GRANT SELECT, DELETE ON TABLE data_donation.els_one_time_password TO cwa_ppdd_retention; 5 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/TestApplication.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @SpringBootApplication 7 | @Configuration 8 | public class TestApplication { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/auth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package app.coronawarn.datadonation.common.protocols; 3 | 4 | option java_multiple_files = true; 5 | 6 | message AuthAndroid { 7 | string safetyNetJwsResult = 1; 8 | string salt = 2; 9 | } 10 | 11 | message AuthIos { 12 | string deviceToken = 1; 13 | string apiToken = 2; 14 | } 15 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V10__alterScanInstanceTable.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM scan_instance 2 | WHERE NOT EXISTS ( 3 | SELECT 1 FROM exposure_window WHERE exposure_window_id=exposure_window.id 4 | ); 5 | 6 | ALTER TABLE scan_instance 7 | ADD CONSTRAINT fk_exposure_window_id FOREIGN KEY (exposure_window_id) 8 | REFERENCES exposure_window(id) 9 | ON DELETE CASCADE; 10 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/tri_state_boolean.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | 6 | enum TriStateBoolean { 7 | TSB_UNSPECIFIED = 0; 8 | TSB_TRUE = 1; 9 | TSB_FALSE = 2; 10 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # This file contains simple default values for the deployment through Docker Compose 2 | # Please change these default values before running docker-compose 3 | 4 | # Docker Compose Postgres settings 5 | POSTGRES_DB=cwa-ppdd 6 | POSTGRES_USER=postgres 7 | POSTGRES_PASSWORD=postgres 8 | 9 | 10 | # Docker Compose PgAdmin settings 11 | PGADMIN_DEFAULT_EMAIL=user@domain.com 12 | PGADMIN_DEFAULT_PASSWORD=password 13 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/OtpStatusException.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | public class OtpStatusException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -5158809661421626293L; 6 | 7 | public OtpStatusException(final String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/DeviceBlocked.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class DeviceBlocked extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 3337613999147226824L; 6 | 7 | public DeviceBlocked() { 8 | super("PPAC failed due to blocked device"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/commons/PpaDataRequestValidationFailed.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.commons; 2 | 3 | public class PpaDataRequestValidationFailed extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -1558962815012631670L; 6 | 7 | public PpaDataRequestValidationFailed(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | # Look for `pom.xml` in the `root` directory. It will recursively scan any submodules. 5 | directory: "/" 6 | schedule: 7 | # Check for updates once a day. This can be changed later on to `weekly` if desired. 8 | interval: "monthly" 9 | # Add reviewers 10 | reviewers: 11 | - "corona-warn-app/cwa-ppa-server-codeowners" 12 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/errors/MetricsDataCouldNotBeStored.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.errors; 2 | 3 | public class MetricsDataCouldNotBeStored extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 2136916612923338677L; 6 | 7 | public MetricsDataCouldNotBeStored(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/commons/PpaDataRequestValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.commons; 2 | 3 | 4 | public interface PpaDataRequestValidator { 5 | 6 | /** 7 | * Based on the provided configuration, perform a validation on the given PPA related payload. 8 | */ 9 | void validate(T payload, Integer maxExposureWindowsToRejectSubmission); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/ApiTokenExpired.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class ApiTokenExpired extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -8382160416477150383L; 6 | 7 | public ApiTokenExpired() { 8 | super("PPAC failed due to expired api token."); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/DeviceTokenInvalid.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class DeviceTokenInvalid extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -3034082672271353058L; 6 | 7 | public DeviceTokenInvalid() { 8 | super("PPAC failed due to invalid device token!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/AndroidIdNotValid.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class AndroidIdNotValid extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -6556735715737848503L; 6 | 7 | 8 | public AndroidIdNotValid() { 9 | super("Android ID length is not 8 bytes"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_android.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | option java_outer_classname = "PPACAndroidProto"; 6 | 7 | message PPACAndroid { 8 | 9 | string safetyNetJws = 1; 10 | 11 | string salt = 2; 12 | } 13 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/FailedJwsParsing.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public final class FailedJwsParsing extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -1579569743042757810L; 6 | 7 | public FailedJwsParsing(Exception e) { 8 | super("Invalid JWS format received", e); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/ApiTokenQuotaExceeded.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class ApiTokenQuotaExceeded extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 7997709572968906061L; 6 | 7 | public ApiTokenQuotaExceeded() { 8 | super("PPAC failed due to Api Token quota exceeded"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/OtpNotFoundException.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | @SuppressWarnings("serial") 4 | public class OtpNotFoundException extends RuntimeException { 5 | 6 | public OtpNotFoundException(final String otp) { 7 | // it's save to be logged, since it's coming from OtpRedemptionRequest#otp 8 | super("OTP '" + otp + "' not found"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V19__alterTableScanInstanceAtTestRegistration.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE scan_instances_at_test_registration 2 | ALTER COLUMN seconds_since_last_scan TYPE INTEGER, 3 | ALTER COLUMN typical_attenuation TYPE INTEGER, 4 | ALTER COLUMN minimum_attenuation TYPE INTEGER; 5 | 6 | ALTER SEQUENCE scan_instances_at_test_registration_id_seq AS bigint; 7 | 8 | ALTER TABLE scan_instances_at_test_registration ALTER COLUMN id TYPE bigint; 9 | 10 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/DeviceTokenRedeemed.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class DeviceTokenRedeemed extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -9084625478944741624L; 6 | 7 | public DeviceTokenRedeemed(final Throwable cause) { 8 | super("PPAC failed due to redeemed device token", cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/DeviceTokenSyntaxError.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class DeviceTokenSyntaxError extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -6225267971601640961L; 6 | 7 | public DeviceTokenSyntaxError(final Throwable cause) { 8 | super("Device token is badly formatted ", cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/CtsProfileMatchRequired.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class CtsProfileMatchRequired extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 3672399350081903614L; 6 | 7 | public CtsProfileMatchRequired() { 8 | super("Cts profile match required in Android attestation response"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/MissingMandatoryAuthenticationFields.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class MissingMandatoryAuthenticationFields extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 6711968113382365103L; 6 | 7 | public MissingMandatoryAuthenticationFields(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_ios.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | option java_outer_classname = "PPACIOSProto"; 6 | 7 | message PPACIOS { 8 | 9 | string deviceToken = 1; 10 | 11 | string apiToken = 2; 12 | 13 | string previousApiToken = 3; 14 | } 15 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/BasicIntegrityIsRequired.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class BasicIntegrityIsRequired extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 2664915373178687868L; 6 | 7 | public BasicIntegrityIsRequired() { 8 | super("Basic Integrity is required in Android attestation response"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/srs-verify/src/test/java/app/coronawarn/datadonation/services/srs/otp/SrsOtpControllerTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.srs.otp; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | final class SrsOtpControllerTest { 8 | 9 | @Test 10 | void testOtpController() { 11 | final SrsOtpController controller = new SrsOtpController(null); 12 | assertThat(controller).isNotNull(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/ApkPackageNameNotAllowed.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class ApkPackageNameNotAllowed extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 8772200600466947124L; 6 | 7 | public ApkPackageNameNotAllowed(String apkPackageName) { 8 | super("APK package not allowed: " + apkPackageName); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V3__alterTables.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE exposure_window 2 | ALTER COLUMN android_api_level TYPE BIGINT, 3 | ALTER COLUMN android_enf_version TYPE BIGINT; 4 | 5 | ALTER TABLE key_submission_metadata_with_client_metadata 6 | ALTER COLUMN android_api_level TYPE BIGINT, 7 | ALTER COLUMN android_enf_version TYPE BIGINT; 8 | 9 | ALTER TABLE client_metadata 10 | ALTER COLUMN android_api_level TYPE BIGINT, 11 | ALTER COLUMN android_enf_version TYPE BIGINT; 12 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V5__createLogTables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE els_one_time_password ( 2 | password VARCHAR(36) PRIMARY KEY, 3 | redemption_timestamp BIGINT, 4 | expiration_timestamp BIGINT NOT NULL, 5 | android_ppac_basic_integrity BOOLEAN, 6 | android_ppac_cts_profile_match BOOLEAN, 7 | android_ppac_evaluation_type_basic BOOLEAN, 8 | android_ppac_evaluation_type_hardware_backed BOOLEAN, 9 | android_ppac_advice BOOLEAN 10 | ); 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/FailedAttestationHostnameValidation.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public final class FailedAttestationHostnameValidation extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -8531642585453016232L; 6 | 7 | public FailedAttestationHostnameValidation(String message, Throwable e) { 8 | super(message, e); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/FailedAttestationTimestampValidation.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class FailedAttestationTimestampValidation extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -8531642585453016232L; 6 | 7 | public FailedAttestationTimestampValidation() { 8 | super("JWS payload timestamp not in validity range"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/BasicEvaluationTypeNotPresent.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | 4 | public class BasicEvaluationTypeNotPresent extends RuntimeException { 5 | 6 | private static final long serialVersionUID = -2513064483236579622L; 7 | 8 | public BasicEvaluationTypeNotPresent() { 9 | super("Evaluation Type BASIC not found in Android attestation response"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/DeleteSaltException.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | 5 | @Profile("test") 6 | public class DeleteSaltException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = -6947727813948118963L; 9 | 10 | public DeleteSaltException(String message, Throwable ex) { 11 | super(message, ex); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/ApkCertificateDigestsNotAllowed.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class ApkCertificateDigestsNotAllowed extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 7913843985526360886L; 6 | 7 | public ApkCertificateDigestsNotAllowed() { 8 | super("APK Certificate Digest Sha256 received which is not part of allowed list"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/ApiTokenAlreadyUsed.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class ApiTokenAlreadyUsed extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 6565450244587356280L; 6 | 7 | public ApiTokenAlreadyUsed(String perDeviceDataLastUpdated) { 8 | super("PPAC failed due to API Token already issued this month: " + perDeviceDataLastUpdated); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | ppa-server 7 | app.coronawarn.data 8 | ${revision} 9 | ../pom.xml 10 | 11 | 12 | common 13 | pom 14 | 15 | 16 | protocols 17 | persistence 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/devicetoken/DeviceTokenRedemptionStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.devicetoken; 2 | 3 | public interface DeviceTokenRedemptionStrategy { 4 | 5 | /** 6 | * How to handle exceptions during OTP redemption. 7 | * 8 | * @param e {@link Exception} to be handled 9 | * @throws InternalError in case redemption fails 10 | */ 11 | void redeem(Exception e) throws InternalError; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: cwa-ppa-server 3 | Upstream-Contact: The Corona Warn App PPA-Server Team 4 | Source: https://github.com/corona-warn-app/cwa-ppa-server 5 | 6 | Files: ** 7 | Copyright: 2021-2022 SAP SE or an SAP affiliate company and Corona-Warn-App contributors 8 | License: Apache-2.0 9 | 10 | Files: .mvn/wrapper mvnw mvnw.cmd 11 | Copyright: 2007-present the original author or authors. 12 | License: Apache-2.0 13 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/config/SecurityLogger.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.config; 2 | 3 | import org.slf4j.Marker; 4 | import org.slf4j.MarkerFactory; 5 | 6 | public interface SecurityLogger { 7 | 8 | Marker SECURITY = MarkerFactory.getMarker("SECURITY"); 9 | 10 | void error(final Exception exception); 11 | 12 | void securityWarn(final Exception exception); 13 | 14 | void successAndroid(final String endpoint); 15 | 16 | void successIos(final String endpoint); 17 | } 18 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/HardwareBackedEvaluationTypeNotPresent.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class HardwareBackedEvaluationTypeNotPresent extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -1834500566508031768L; 6 | 7 | public HardwareBackedEvaluationTypeNotPresent() { 8 | super("Evaluation Type HARDWARE_BACKED not found in Android attestation response"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/ExternalServiceError.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | import feign.FeignException; 4 | 5 | public class ExternalServiceError extends InternalServerError { 6 | 7 | private static final long serialVersionUID = -1420803245315169479L; 8 | 9 | public ExternalServiceError(FeignException cause) { 10 | super("Responded with HTTP-" + cause.status() + ": " + cause.getMessage(), cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/license-analysis.yml: -------------------------------------------------------------------------------- 1 | name: license-analysis-workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/** 8 | pull_request: 9 | branches: 10 | - main 11 | - release/** 12 | 13 | jobs: 14 | license-analysis-job: 15 | name: REUSE Compliance Check 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | - name: REUSE Compliance Check 21 | uses: fsfe/reuse-action@v1.1 -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/NonceCalculationError.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class NonceCalculationError extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -8400452065767821216L; 6 | 7 | public NonceCalculationError(Exception cause) { 8 | super("Could not recaculate nonce. ", cause); 9 | } 10 | 11 | public NonceCalculationError(String message) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /setup/create-users.sql: -------------------------------------------------------------------------------- 1 | CREATE USER "flyway" WITH INHERIT IN ROLE cwa_ppdd_flyway PASSWORD ''; 2 | CREATE USER "ppac" WITH INHERIT IN ROLE cwa_ppdd_ppac PASSWORD ''; 3 | CREATE USER "edus" WITH INHERIT IN ROLE cwa_ppdd_edus PASSWORD ''; 4 | CREATE USER "retention" WITH INHERIT IN ROLE cwa_ppdd_retention PASSWORD ''; 5 | CREATE USER "els_verify" WITH INHERIT IN ROLE cwa_ppdd_els_verify PASSWORD ''; 6 | CREATE USER "srs_verify" WITH INHERIT IN ROLE cwa_ppdd_srs_verify PASSWORD ''; 7 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/NonceCouldNotBeVerified.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public class NonceCouldNotBeVerified extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -288077614896557469L; 6 | 7 | public NonceCouldNotBeVerified(String message) { 8 | super(message); 9 | } 10 | 11 | public NonceCouldNotBeVerified(String message, Exception cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file provides an overview of code owners in this repository. 2 | 3 | # Each line is a file pattern followed by one or more owners. 4 | # The last matching pattern has the most precedence. 5 | # For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. 6 | 7 | # These are the default owners for the whole content of this repository. The default owners are automatically added as reviewers when you open a pull request, unless different owners are specified in the file. 8 | * @corona-warn-app/cwa-ppa-server-codeowners 9 | -------------------------------------------------------------------------------- /common/persistence/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logging: 3 | level: 4 | org: 5 | springframework: info 6 | root: info 7 | spring: 8 | flyway: 9 | enabled: true 10 | locations: classpath:/db/migration 11 | schemas: data_donation 12 | main: 13 | banner-mode: off 14 | datasource: 15 | enabled: true 16 | url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw 17 | hikari: 18 | schema: data_donation 19 | test: 20 | database: 21 | # Use datasource as defined above. 22 | replace: none 23 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/SaltNotValidAnymore.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ppac.android.SaltData; 4 | 5 | public class SaltNotValidAnymore extends RuntimeException { 6 | 7 | private static final long serialVersionUID = 1710485742505301467L; 8 | 9 | public SaltNotValidAnymore(SaltData saltData) { 10 | super("A salt was sent with an expired validity: " + saltData); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/devicetoken/LoadTestDeviceTokenRedemptionStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.devicetoken; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | @Profile("loadtest") 8 | public class LoadTestDeviceTokenRedemptionStrategy implements DeviceTokenRedemptionStrategy { 9 | 10 | @Override 11 | public void redeem(Exception e) { 12 | // do nothing here 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services/edus/src/main/java/app/coronawarn/datadonation/services/edus/otp/OtpRedemptionRequest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.edus.otp; 2 | 3 | import javax.validation.constraints.Pattern; 4 | 5 | public class OtpRedemptionRequest { 6 | 7 | /** 8 | * UUID. 9 | */ 10 | @Pattern(regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}") 11 | private String otp; 12 | 13 | public String getOtp() { 14 | return otp; 15 | } 16 | 17 | public void setOtp(String otp) { 18 | this.otp = otp; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/FailedSignatureVerification.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | public final class FailedSignatureVerification extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 5078963545906035590L; 6 | 7 | public FailedSignatureVerification(String message) { 8 | super(message); 9 | } 10 | 11 | public FailedSignatureVerification(String message, Throwable e) { 12 | super(message, e); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/errors/InternalServerError.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.errors; 2 | 3 | public class InternalServerError extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 3035093883104293913L; 6 | 7 | public InternalServerError(final Throwable cause) { 8 | super("Internal error occurred: " + cause.getMessage(), cause); 9 | } 10 | 11 | InternalServerError(String msg, final Throwable cause) { 12 | super(msg, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services/els-verify/src/main/java/app/coronawarn/datadonation/services/els/otp/ElsOtpRedemptionRequest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.els.otp; 2 | 3 | import javax.validation.constraints.Pattern; 4 | 5 | public class ElsOtpRedemptionRequest { 6 | 7 | /** 8 | * UUID. 9 | */ 10 | @Pattern(regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}") 11 | private String otp; 12 | 13 | public String getOtp() { 14 | return otp; 15 | } 16 | 17 | public void setOtp(String otp) { 18 | this.otp = otp; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/java/app/coronawarn/datadonation/services/srs/otp/SrsOtpRedemptionRequest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.srs.otp; 2 | 3 | import javax.validation.constraints.Pattern; 4 | 5 | public class SrsOtpRedemptionRequest { 6 | 7 | /** 8 | * UUID. 9 | */ 10 | @Pattern(regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}") 11 | private String otp; 12 | 13 | public String getOtp() { 14 | return otp; 15 | } 16 | 17 | public void setOtp(String otp) { 18 | this.otp = otp; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /services/edus/src/main/java/app/coronawarn/datadonation/services/edus/otp/OtpState.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.edus.otp; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | 5 | public enum OtpState { 6 | EXPIRED("expired"), 7 | REDEEMED("redeemed"), 8 | VALID("valid"); 9 | 10 | private String state; 11 | 12 | OtpState(String state) { 13 | this.state = state; 14 | } 15 | 16 | @JsonValue 17 | final String state() { 18 | return this.state; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return state; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/DeviceQuotaExceeded.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.FORBIDDEN) 7 | public class DeviceQuotaExceeded extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 8228950515673394141L; 10 | 11 | public DeviceQuotaExceeded() { 12 | super("Device quota exceeded"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/edus_otp_request_ios.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_ios.proto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/edus_otp.proto"; 7 | 8 | message EDUSOneTimePasswordRequestIOS { 9 | 10 | PPACIOS authentication = 1; 11 | 12 | EDUSOneTimePassword payload = 2; 13 | } -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/els_otp_request_ios.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_ios.proto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/els_otp.proto"; 7 | 8 | message ELSOneTimePasswordRequestIOS { 9 | 10 | PPACIOS authentication = 1; 11 | 12 | ELSOneTimePassword payload = 2; 13 | } -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/specific/postgresql/V22__grantSRSPerimissions.sql: -------------------------------------------------------------------------------- 1 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.srs_one_time_password TO cwa_ppdd_ppac; 2 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.android_id TO cwa_ppdd_ppac; 3 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.srs_one_time_password TO cwa_ppdd_srs_verify; 4 | GRANT SELECT, INSERT, UPDATE ON TABLE data_donation.android_id TO cwa_ppdd_srs_verify; 5 | GRANT SELECT, DELETE ON TABLE data_donation.srs_one_time_password TO cwa_ppdd_retention; 6 | GRANT SELECT, DELETE ON TABLE data_donation.android_id TO cwa_ppdd_retention; 7 | -------------------------------------------------------------------------------- /scripts/generate_OTPs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # whatever you've started locally 4 | curl --cert certificate.crt --key private.pem --insecure https://localhost:8080/version/v1/gen/srs/10/30 5 | 6 | # docker-compose up: 7 | 8 | # srs-verify-1 9 | curl -v --cert certificate.crt --key private.pem --insecure https://localhost:8105/version/v1/gen/srs/10/30 10 | 11 | # edus-1 12 | curl -v --cert certificate.crt --key private.pem --insecure https://localhost:8103/version/v1/gen/otp/10/30 13 | 14 | # els-verify-1 15 | curl -v --cert certificate.crt --key private.pem --insecure https://localhost:8106/version/v1/gen/els/10/30 16 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/timestamp/NoOpTimestampVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.timestamp; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | @Profile("loadtest") 8 | public final class NoOpTimestampVerificationStrategy implements TimestampVerificationStrategy { 9 | 10 | @Override 11 | public void validateTimestamp(long attestationTimestamp) { 12 | // do nothing during load testing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/05_other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4AC Other" 3 | about: For conceptual questions, please consider opening an issue in the documentation repository. 4 | labels: other 5 | --- 6 | 7 | 13 | -------------------------------------------------------------------------------- /services/edus/src/test/java/app/coronawarn/datadonation/services/edus/otp/StringUtils.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.edus.otp; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | public class StringUtils { 7 | 8 | /** 9 | * Returns an stringified object. 10 | * 11 | * @param obj Object to be converted to string 12 | * @return Json String 13 | */ 14 | public static String asJsonString(final Object obj) throws JsonProcessingException { 15 | return new ObjectMapper().writeValueAsString(obj); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/OtpState.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | 5 | public enum OtpState { 6 | EXPIRED("expired"), 7 | REDEEMED("redeemed"), 8 | VALID("valid"); 9 | 10 | private final String state; 11 | 12 | OtpState(String state) { 13 | this.state = state; 14 | } 15 | 16 | @JsonValue 17 | final String state() { 18 | return this.state; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return state; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/els_otp_request_android.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_android.proto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/els_otp.proto"; 7 | 8 | message ELSOneTimePasswordRequestAndroid { 9 | 10 | PPACAndroid authentication = 1; 11 | 12 | ELSOneTimePassword payload = 2; 13 | } -------------------------------------------------------------------------------- /services/els-verify/src/test/java/app/coronawarn/datadonation/services/els/otp/StringUtils.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.els.otp; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | public class StringUtils { 7 | 8 | /** 9 | * Returns an stringified object. 10 | * 11 | * @param obj Object to be converted to string 12 | * @return Json String 13 | */ 14 | public static String asJsonString(final Object obj) throws JsonProcessingException { 15 | return new ObjectMapper().writeValueAsString(obj); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/errors/AndroidIdUpsertError.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.errors; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 7 | public class AndroidIdUpsertError extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 6883040825312105258L; 10 | 11 | public AndroidIdUpsertError() { 12 | super("Android ID could not be persisted"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services/srs-verify/src/test/java/app/coronawarn/datadonation/services/srs/otp/StringUtils.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.srs.otp; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | public class StringUtils { 7 | 8 | /** 9 | * Returns an stringified object. 10 | * 11 | * @param obj Object to be converted to string 12 | * @return Json String 13 | */ 14 | public static String asJsonString(final Object obj) throws JsonProcessingException { 15 | return new ObjectMapper().writeValueAsString(obj); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/edus_otp_request_android.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_android.proto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/edus_otp.proto"; 7 | 8 | message EDUSOneTimePasswordRequestAndroid { 9 | 10 | PPACAndroid authentication = 1; 11 | 12 | EDUSOneTimePassword payload = 2; 13 | } -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/domain/ElsOneTimePassword.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain; 2 | 3 | import javax.validation.constraints.Size; 4 | 5 | public class ElsOneTimePassword extends OneTimePassword { 6 | 7 | /** 8 | * No argument constructor. 9 | */ 10 | public ElsOneTimePassword() { 11 | } 12 | 13 | /** 14 | * Constructs the {@link ElsOneTimePassword}. 15 | * 16 | * @param password The otp to store. 17 | */ 18 | public ElsOneTimePassword(@Size(min = 36, max = 36) String password) { 19 | super(password); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/domain/SrsOneTimePassword.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain; 2 | 3 | import javax.validation.constraints.Size; 4 | 5 | public class SrsOneTimePassword extends OneTimePassword { 6 | 7 | /** 8 | * No argument constructor. 9 | */ 10 | public SrsOneTimePassword() { 11 | } 12 | 13 | /** 14 | * Constructs the {@link SrsOneTimePassword}. 15 | * 16 | * @param password The otp to store. 17 | */ 18 | public SrsOneTimePassword(@Size(min = 36, max = 36) String password) { 19 | super(password); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/srs_otp_request_ios.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_ios.proto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/srs_otp.proto"; 7 | 8 | message SRSOneTimePasswordRequestIOS { 9 | 10 | PPACIOS authentication = 1; 11 | 12 | SRSOneTimePassword payload = 2; 13 | 14 | bytes requestPadding = 3; 15 | } -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/ppa_data_request_ios.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | option java_outer_classname = "PPADataRequestIOSProto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_ios.proto"; 7 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppa_data.proto"; 8 | 9 | message PPADataRequestIOS { 10 | 11 | PPACIOS authentication = 1; 12 | 13 | PPADataIOS payload = 2; 14 | } 15 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/config/TestBeanConfig.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.config; 2 | 3 | import app.coronawarn.datadonation.services.ppac.android.controller.RequestExecutor; 4 | import org.springframework.boot.test.web.client.TestRestTemplate; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class TestBeanConfig { 10 | 11 | @Bean 12 | public RequestExecutor requestExecutor(TestRestTemplate testRestTemplate) { 13 | return new RequestExecutor(testRestTemplate); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/ppa_data_request_android.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | option java_outer_classname = "PPADataRequestAndroidProto"; 6 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_android.proto"; 7 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppa_data.proto"; 8 | 9 | message PPADataRequestAndroid { 10 | 11 | PPACAndroid authentication = 1; 12 | 13 | PPADataAndroid payload = 2; 14 | } 15 | -------------------------------------------------------------------------------- /common/protocols/src/main/proto/app/coronawarn/datadonation/common/protocols/internal/ppdd/srs_otp_request_android.proto: -------------------------------------------------------------------------------- 1 | // This file is auto-generated, DO NOT make any changes here 2 | syntax = "proto3"; 3 | package app.coronawarn.datadonation.common.protocols.internal.ppdd; 4 | option java_multiple_files = true; 5 | import "app/coronawarn/datadonation/common/protocols/internal/ppdd/ppac_android.proto"; 6 | 7 | message SRSOneTimePasswordRequestAndroid { 8 | 9 | PPACAndroid authentication = 1; 10 | 11 | SRSOneTimePassword payload = 2; 12 | 13 | bytes requestPadding = 3; 14 | 15 | message SRSOneTimePassword { 16 | string otp = 1; 17 | bytes androidId = 2; 18 | } 19 | } -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/config/ThirdPartyBeanRegistration.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.config; 2 | 3 | import org.apache.http.conn.ssl.DefaultHostnameVerifier; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * Declare objects from third party libraries which are not component scanned as Spring beans. 9 | */ 10 | @Configuration 11 | public class ThirdPartyBeanRegistration { 12 | 13 | @Bean 14 | public DefaultHostnameVerifier hostnameVerifier() { 15 | return new DefaultHostnameVerifier(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/OtpService.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.OneTimePassword; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class OtpService extends AbstractOtpService { 9 | 10 | /** 11 | * Constructs the OtpService. 12 | * 13 | * @param otpRepository The OTP Repository. 14 | */ 15 | protected OtpService(CrudRepository otpRepository) { 16 | super(otpRepository); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/controller/IosDelayManager.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.controller; 2 | 3 | import app.coronawarn.datadonation.services.ppac.commons.AbstractDelayManager; 4 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * {@link IosDelayManager} instances manage the response delay in the processing of fake (or "dummy") requests. 9 | */ 10 | @Component 11 | public class IosDelayManager extends AbstractDelayManager { 12 | 13 | public IosDelayManager(final PpacConfiguration config) { 14 | super(config); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V21__featureSelfReportSubmissions.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS srs_one_time_password ( 2 | password VARCHAR(36) PRIMARY KEY, 3 | redemption_timestamp BIGINT, 4 | expiration_timestamp BIGINT NOT NULL, 5 | android_ppac_basic_integrity BOOLEAN, 6 | android_ppac_cts_profile_match BOOLEAN, 7 | android_ppac_evaluation_type_basic BOOLEAN, 8 | android_ppac_evaluation_type_hardware_backed BOOLEAN 9 | ); 10 | 11 | CREATE TABLE IF NOT EXISTS android_id ( 12 | id CHAR(44) PRIMARY KEY, 13 | expiration_date BIGINT NOT NULL, 14 | last_used_srs BIGINT 15 | ); 16 | 17 | ALTER TABLE api_token ADD COLUMN IF NOT EXISTS last_used_srs BIGINT; 18 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/ElsOtpService.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ElsOneTimePassword; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class ElsOtpService extends AbstractOtpService { 9 | 10 | /** 11 | * Constructs the ElsOtpService. 12 | * 13 | * @param otpRepository The OTP Repository. 14 | */ 15 | protected ElsOtpService(CrudRepository otpRepository) { 16 | super(otpRepository); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/AndroidDelayManager.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller; 2 | 3 | import app.coronawarn.datadonation.services.ppac.commons.AbstractDelayManager; 4 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * {@link AndroidDelayManager} instances manage the response delay in the processing of fake (or "dummy") requests. 9 | */ 10 | @Component 11 | public class AndroidDelayManager extends AbstractDelayManager { 12 | 13 | public AndroidDelayManager(final PpacConfiguration config) { 14 | super(config); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/SrsOtpService.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.SrsOneTimePassword; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class SrsOtpService extends AbstractOtpService { 9 | 10 | /** 11 | * Constructs the SrsOtpService. 12 | * 13 | * @param otpRepository The SRS OTP Repository. 14 | */ 15 | protected SrsOtpService(final CrudRepository otpRepository) { 16 | super(otpRepository); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services/edus/src/main/resources/application-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | flyway: 4 | password: ${POSTGRESQL_PASSWORD_FLYWAY} 5 | user: ${POSTGRESQL_USER_FLYWAY} 6 | locations: classpath:/db/migration, classpath:/db/specific/{vendor} 7 | schemas: data_donation 8 | datasource: 9 | driver-class-name: org.postgresql.Driver 10 | username: ${POSTGRESQL_USER_EDUS} 11 | password: ${POSTGRESQL_PASSWORD_EDUS} 12 | hikari: 13 | schema: data_donation 14 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE}?ssl=true&sslmode=verify-full&sslrootcert=${SSL_POSTGRES_CERTIFICATE_PATH}&sslcert=${SSL_EDUS_CERTIFICATE_PATH}&sslkey=${SSL_EDUS_PRIVATE_KEY_PATH} 15 | 16 | -------------------------------------------------------------------------------- /services/ppac/src/main/resources/application-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | flyway: 4 | password: ${POSTGRESQL_PASSWORD_FLYWAY} 5 | user: ${POSTGRESQL_USER_FLYWAY} 6 | locations: classpath:/db/migration, classpath:/db/specific/{vendor} 7 | schemas: data_donation 8 | datasource: 9 | driver-class-name: org.postgresql.Driver 10 | username: ${POSTGRESQL_USER_DATA} 11 | password: ${POSTGRESQL_PASSWORD_DATA} 12 | hikari: 13 | schema: data_donation 14 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE}?ssl=true&sslmode=verify-full&sslrootcert=${SSL_POSTGRES_CERTIFICATE_PATH}&sslcert=${SSL_DATA_CERTIFICATE_PATH}&sslkey=${SSL_DATA_PRIVATE_KEY_PATH} 15 | 16 | -------------------------------------------------------------------------------- /services/els-verify/src/main/resources/application-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | flyway: 4 | password: ${POSTGRESQL_PASSWORD_FLYWAY} 5 | user: ${POSTGRESQL_USER_FLYWAY} 6 | locations: classpath:/db/migration, classpath:/db/specific/{vendor} 7 | schemas: data_donation 8 | datasource: 9 | driver-class-name: org.postgresql.Driver 10 | username: ${POSTGRESQL_USER_ELS} 11 | password: ${POSTGRESQL_PASSWORD_ELS} 12 | hikari: 13 | schema: data_donation 14 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE}?ssl=true&sslmode=verify-full&sslrootcert=${SSL_POSTGRES_CERTIFICATE_PATH}&sslcert=${SSL_ELS_CERTIFICATE_PATH}&sslkey=${SSL_ELS_PRIVATE_KEY_PATH} 15 | 16 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/resources/application-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | flyway: 4 | password: ${POSTGRESQL_PASSWORD_FLYWAY} 5 | user: ${POSTGRESQL_USER_FLYWAY} 6 | locations: classpath:/db/migration, classpath:/db/specific/{vendor} 7 | schemas: data_donation 8 | datasource: 9 | driver-class-name: org.postgresql.Driver 10 | username: ${POSTGRESQL_USER_SRS} 11 | password: ${POSTGRESQL_PASSWORD_SRS} 12 | hikari: 13 | schema: data_donation 14 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE}?ssl=true&sslmode=verify-full&sslrootcert=${SSL_POSTGRES_CERTIFICATE_PATH}&sslcert=${SSL_SRS_CERTIFICATE_PATH}&sslkey=${SSL_SRS_PRIVATE_KEY_PATH} 15 | 16 | -------------------------------------------------------------------------------- /services/retention/src/main/resources/application-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | flyway: 4 | password: ${POSTGRESQL_PASSWORD_FLYWAY} 5 | user: ${POSTGRESQL_USER_FLYWAY} 6 | locations: classpath:/db/migration, classpath:/db/specific/{vendor} 7 | schemas: data_donation 8 | datasource: 9 | driver-class-name: org.postgresql.Driver 10 | username: ${POSTGRESQL_USER_RETENTION} 11 | password: ${POSTGRESQL_PASSWORD_RETENTION} 12 | hikari: 13 | schema: data_donation 14 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE}?ssl=true&sslmode=verify-full&sslrootcert=${SSL_POSTGRES_CERTIFICATE_PATH}&sslcert=${SSL_RETENTION_CERTIFICATE_PATH}&sslkey=${SSL_RETENTION_PRIVATE_KEY_PATH} 15 | 16 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/persistence/service/OtpStateTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import net.minidev.json.JSONValue; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class OtpStateTest { 9 | 10 | @Test 11 | void testStateReturnsValidJson() { 12 | OtpState valid = OtpState.VALID; 13 | OtpState expired = OtpState.EXPIRED; 14 | OtpState redeemed = OtpState.REDEEMED; 15 | 16 | assertThat(JSONValue.isValidJson(valid.state())).isTrue(); 17 | assertThat(JSONValue.isValidJson(expired.state())).isTrue(); 18 | assertThat(JSONValue.isValidJson(redeemed.state())).isTrue(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U00002753 Question" 3 | about: If you have questions about pieces of the code or documentation for this component, please post them here. 4 | labels: question 5 | --- 6 | 7 | 13 | 14 | ## Your Question 15 | 16 | 17 | 18 | * Source File: 19 | * Line(s): 20 | * Question: 21 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/specific/postgresql/V15__grantPermissionForNewTablesPPAExtention.sql: -------------------------------------------------------------------------------- 1 | GRANT SELECT, INSERT, UPDATE ON TABLE 2 | data_donation.exposure_window_test_result, 3 | data_donation.exposure_windows_at_test_registration, 4 | data_donation.scan_instances_at_test_registration, 5 | data_donation.summarized_exposure_windows_with_user_metadata 6 | TO cwa_ppdd_ppac; 7 | 8 | GRANT ALL ON ALL SEQUENCES IN SCHEMA data_donation TO cwa_ppdd_ppac; 9 | 10 | GRANT SELECT, DELETE ON TABLE 11 | data_donation.exposure_window_test_result, 12 | data_donation.exposure_windows_at_test_registration, 13 | data_donation.scan_instances_at_test_registration, 14 | data_donation.summarized_exposure_windows_with_user_metadata 15 | TO cwa_ppdd_retention; 16 | -------------------------------------------------------------------------------- /scripts/certificate.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB7zCCAZagAwIBAgIUWcSUQobZDpmbHWGWlf5Pm9Pc0aMwCgYIKoZIzj0EAwIw 3 | LzESMBAGA1UEAwwJbG9jYWxob3N0MRkwFwYDVQQLDBBDV0EtQmFja2VuZC1UZWFt 4 | MB4XDTIyMTEzMDE1MDY1NloXDTMyMTIwMTE1MDY1NlowLzESMBAGA1UEAwwJbG9j 5 | YWxob3N0MRkwFwYDVQQLDBBDV0EtQmFja2VuZC1UZWFtMFkwEwYHKoZIzj0CAQYI 6 | KoZIzj0DAQcDQgAE9/HUs+ssvOdmv+BZPjubaUiYOWYTd5iRMopbdBzpEPXbyQBS 7 | mOFesVJ7y3GTU/1ql9FuIrqB7YBkhZZExPEqE6OBjzCBjDBrBgNVHREEZDBiggls 8 | b2NhbGhvc3SCCnN1Ym1pc3Npb26CBnVwbG9hZIIIZG93bmxvYWSCCGNhbGxiYWNr 9 | ggxkaXN0cmlidXRpb26CFGhvc3QuZG9ja2VyLmludGVybmFsggllZmdzLWZha2Uw 10 | HQYDVR0OBBYEFMiByCNtotATdzC45jeZZ7YhSopgMAoGCCqGSM49BAMCA0cAMEQC 11 | IElURxTgk2l8KFXy2adODVUJxIwGK59Ykze6/Kw1aifFAiBRAySDEuJg3vVoru7D 12 | syElXPvh4LtB+d5pIvnRN/xWFw== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/salt/LoadTestSaltVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.salt; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @Profile("loadtest") 10 | public class LoadTestSaltVerificationStrategy implements SaltVerificationStrategy { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(LoadTestSaltVerificationStrategy.class); 13 | 14 | @Override 15 | public void validateSalt(String saltString) { 16 | // skip this process during load testing 17 | LOGGER.debug("Salt received: {}", saltString); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V9__alterMetadataTables.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE key_submission_metadata_with_client_metadata ADD submitted_with_check_ins BOOLEAN; 2 | 3 | ALTER TABLE key_submission_metadata_with_user_metadata 4 | ADD submitted_after_rapid_antigen_test BOOLEAN NOT NULL DEFAULT FALSE, 5 | ADD pt_days_since_most_recent_date_at_risk_level INTEGER, 6 | ADD pt_hours_since_high_risk_warning INTEGER; 7 | 8 | ALTER TABLE exposure_risk_metadata 9 | ADD pt_risk_level INTEGER, 10 | ADD pt_risk_level_changed BOOLEAN, 11 | ADD pt_most_recent_date_at_risk_level DATE, 12 | ADD pt_most_recent_date_changed BOOLEAN; 13 | 14 | ALTER TABLE test_result_metadata 15 | ADD pt_risk_level INTEGER, 16 | ADD pt_days_since_most_recent_date_at_risk_level INTEGER, 17 | ADD pt_hours_since_high_risk_warning INTEGER; 18 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V20__alterFKtoBigInt.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE scan_instance ALTER COLUMN exposure_window_id TYPE BIGINT; 2 | 3 | ALTER TABLE exposure_windows_at_test_registration ALTER COLUMN id TYPE BIGINT, 4 | ALTER COLUMN exposure_window_test_result_id TYPE BIGINT; 5 | ALTER SEQUENCE exposure_windows_at_test_registration_id_seq AS BIGINT; 6 | 7 | ALTER TABLE scan_instances_at_test_registration ALTER COLUMN exposure_window_id TYPE BIGINT; 8 | 9 | ALTER TABLE exposure_window_test_result ALTER COLUMN id TYPE BIGINT; 10 | ALTER SEQUENCE exposure_window_test_result_id_seq AS BIGINT; 11 | 12 | ALTER TABLE summarized_exposure_windows_with_user_metadata ALTER COLUMN id TYPE BIGINT; 13 | ALTER SEQUENCE summarized_exposure_windows_with_user_metadata_id_seq AS BIGINT; 14 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/domain/ppac/android/SaltData.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain.ppac.android; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Table; 5 | 6 | @Table("salt") 7 | public class SaltData { 8 | 9 | @Id 10 | private String salt; 11 | private final Long createdAt; 12 | 13 | public SaltData(String salt, Long createdAt) { 14 | this.salt = salt; 15 | this.createdAt = createdAt; 16 | } 17 | 18 | public String getSalt() { 19 | return salt; 20 | } 21 | 22 | public Long getCreatedAt() { 23 | return createdAt; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Salt [salt=" + salt + ", createdAt=" + createdAt + "]"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/signature/SignatureVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.signature; 2 | 3 | import com.google.api.client.json.webtoken.JsonWebSignature; 4 | import java.security.GeneralSecurityException; 5 | import java.security.cert.X509Certificate; 6 | 7 | public interface SignatureVerificationStrategy { 8 | 9 | /** 10 | * Verify that X509 certificates (chain) which are included in the 'x5c' header of the JWS (and 11 | * used to validate the JWS Signature) are trusted by the default JVM TrustManager. Parse and 12 | * return the leaf {@link X509Certificate} object in the chain in case it is verified. 13 | */ 14 | public X509Certificate verifySignature(JsonWebSignature jws) throws GeneralSecurityException; 15 | } 16 | -------------------------------------------------------------------------------- /common/persistence/src/test/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | Generator 3 | 4 | jar 5 | 6 | false 7 | 8 | 9 | ${project.build.directory}/test-classes 10 | / 11 | 12 | **/AndroidIdServiceTest.class 13 | 14 | true 15 | 16 | 17 | ${project.build.directory}/classes 18 | / 19 | 20 | **/AndroidIdService*.class 21 | 22 | true 23 | 24 | 25 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V18__alterTablesWithTechnicalMetadata.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE exposure_windows_at_test_registration 2 | ADD android_ppac_basic_integrity BOOLEAN, 3 | ADD android_ppac_cts_profile_match BOOLEAN, 4 | ADD android_ppac_evaluation_type_basic BOOLEAN, 5 | ADD android_ppac_evaluation_type_hardware_backed BOOLEAN; 6 | 7 | ALTER TABLE scan_instances_at_test_registration 8 | ADD android_ppac_basic_integrity BOOLEAN, 9 | ADD android_ppac_cts_profile_match BOOLEAN, 10 | ADD android_ppac_evaluation_type_basic BOOLEAN, 11 | ADD android_ppac_evaluation_type_hardware_backed BOOLEAN; 12 | 13 | ALTER TABLE scan_instance 14 | ADD android_ppac_basic_integrity BOOLEAN, 15 | ADD android_ppac_cts_profile_match BOOLEAN, 16 | ADD android_ppac_evaluation_type_basic BOOLEAN, 17 | ADD android_ppac_evaluation_type_hardware_backed BOOLEAN; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | .idea 3 | *.iws 4 | *.iml 5 | *.ipr 6 | .idea_modules/ 7 | .runconfigs/ 8 | .run/ 9 | 10 | ### NetBeans ### 11 | /nbproject/private/ 12 | /nbbuild/ 13 | /nbdist/ 14 | 15 | ### VS Code ### 16 | .vscode/ 17 | 18 | # Compiled class file 19 | *.class 20 | 21 | # Log file 22 | *.log 23 | 24 | # BlueJ files 25 | *.ctxt 26 | 27 | # Mobile Tools for Java (J2ME) 28 | .mtj.tmp/ 29 | 30 | # Package Files # 31 | *.jar 32 | *.war 33 | *.nar 34 | *.ear 35 | *.zip 36 | *.tar.gz 37 | *.rar 38 | 39 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 40 | hs_err_pid* 41 | 42 | # Project specifics 43 | target/ 44 | 45 | .DS_Store 46 | 47 | out/ 48 | 49 | .settings 50 | .project 51 | .classpath 52 | .factorypath 53 | .checkstyle 54 | 55 | pom.xml.versionsBackup 56 | **/.flattened-pom.xml 57 | *.p8 58 | /temp/ 59 | 60 | services/*/**/log4j2*.xml 61 | -------------------------------------------------------------------------------- /services/edus/src/main/resources/bootstrap-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: cwa-data 5 | cloud: 6 | vault: 7 | ssl: 8 | trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH} 9 | trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD} 10 | enabled: true 11 | generic: 12 | enabled: false 13 | kv: 14 | enabled: true 15 | backend: ${VAULT_BACKEND} 16 | profile-separator: '/' 17 | application-name: 'edus' 18 | default-context: '' 19 | profiles: cloud 20 | fail-fast: true 21 | authentication: KUBERNETES 22 | kubernetes: 23 | role: ${VAULT_ROLE} 24 | kubernetes-path: kubernetes 25 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 26 | uri: ${VAULT_URI} 27 | connection-timeout: 5000 28 | read-timeout: 15000 29 | config: 30 | order: -10 31 | -------------------------------------------------------------------------------- /services/ppac/src/main/resources/bootstrap-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: cwa-data 5 | cloud: 6 | vault: 7 | ssl: 8 | trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH} 9 | trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD} 10 | enabled: true 11 | generic: 12 | enabled: false 13 | kv: 14 | enabled: true 15 | backend: ${VAULT_BACKEND} 16 | profile-separator: '/' 17 | application-name: 'data' 18 | default-context: '' 19 | profiles: cloud 20 | fail-fast: true 21 | authentication: KUBERNETES 22 | kubernetes: 23 | role: ${VAULT_ROLE} 24 | kubernetes-path: kubernetes 25 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 26 | uri: ${VAULT_URI} 27 | connection-timeout: 5000 28 | read-timeout: 15000 29 | config: 30 | order: -10 31 | -------------------------------------------------------------------------------- /.github/workflows/markdown-analysis.yml: -------------------------------------------------------------------------------- 1 | name: markdown-analysis-workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/** 8 | pull_request: 9 | branches: 10 | - main 11 | - release/** 12 | paths: 13 | - '**/*.md' 14 | 15 | jobs: 16 | markdown-analysis-job: 17 | runs-on: ubuntu-latest 18 | name: 'github actions: markdownlint' 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | - name: markdownlint 23 | uses: nosborn/github-action-markdown-cli@v1.1.1 24 | with: 25 | files: . 26 | config_file: ./codestyle/.markdownlint.yml 27 | - name: markdown link check 28 | uses: gaurav-nelson/github-action-markdown-link-check@v1 29 | with: 30 | config-file: ./codestyle/.markdown-link-check.json -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/commons/web/DataSubmissionResponse.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.commons.web; 2 | 3 | import app.coronawarn.datadonation.services.ppac.logging.PpacErrorCode; 4 | 5 | public class DataSubmissionResponse { 6 | 7 | private PpacErrorCode errorCode; 8 | 9 | public PpacErrorCode getErrorCode() { 10 | return errorCode; 11 | } 12 | 13 | /** 14 | * Simple helper method to create a DataSubmissionResponse with the provided ErrorCode {@link PpacErrorCode}. 15 | * 16 | * @param errorCode the provided ErrorCode. 17 | * @return a new instance of DataSubmissionResponse. 18 | */ 19 | public static DataSubmissionResponse of(PpacErrorCode errorCode) { 20 | DataSubmissionResponse dataSubmissionResponse = new DataSubmissionResponse(); 21 | dataSubmissionResponse.errorCode = errorCode; 22 | return dataSubmissionResponse; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services/retention/src/main/resources/bootstrap-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: cwa-data 5 | cloud: 6 | vault: 7 | ssl: 8 | trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH} 9 | trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD} 10 | enabled: true 11 | generic: 12 | enabled: false 13 | kv: 14 | enabled: true 15 | backend: ${VAULT_BACKEND} 16 | profile-separator: '/' 17 | application-name: 'retention' 18 | default-context: '' 19 | profiles: cloud 20 | fail-fast: true 21 | authentication: KUBERNETES 22 | kubernetes: 23 | role: ${VAULT_ROLE} 24 | kubernetes-path: kubernetes 25 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 26 | uri: ${VAULT_URI} 27 | connection-timeout: 5000 28 | read-timeout: 15000 29 | config: 30 | order: -10 31 | -------------------------------------------------------------------------------- /services/els-verify/src/main/resources/bootstrap-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: cwa-data 5 | cloud: 6 | vault: 7 | ssl: 8 | trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH} 9 | trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD} 10 | enabled: true 11 | generic: 12 | enabled: false 13 | kv: 14 | enabled: true 15 | backend: ${VAULT_BACKEND} 16 | profile-separator: '/' 17 | application-name: 'els-verify' 18 | default-context: '' 19 | profiles: cloud 20 | fail-fast: true 21 | authentication: KUBERNETES 22 | kubernetes: 23 | role: ${VAULT_ROLE} 24 | kubernetes-path: kubernetes 25 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 26 | uri: ${VAULT_URI} 27 | connection-timeout: 5000 28 | read-timeout: 15000 29 | config: 30 | order: -10 31 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/resources/bootstrap-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: cwa-data 5 | cloud: 6 | vault: 7 | ssl: 8 | trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH} 9 | trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD} 10 | enabled: true 11 | generic: 12 | enabled: false 13 | kv: 14 | enabled: true 15 | backend: ${VAULT_BACKEND} 16 | profile-separator: '/' 17 | application-name: 'srs-verify' 18 | default-context: '' 19 | profiles: cloud 20 | fail-fast: true 21 | authentication: KUBERNETES 22 | kubernetes: 23 | role: ${VAULT_ROLE} 24 | kubernetes-path: kubernetes 25 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 26 | uri: ${VAULT_URI} 27 | connection-timeout: 5000 28 | read-timeout: 15000 29 | config: 30 | order: -10 31 | -------------------------------------------------------------------------------- /setup/create-analytics-user.sql: -------------------------------------------------------------------------------- 1 | CREATE ROLE cwa_ppdd_analytics 2 | NOLOGIN 3 | NOSUPERUSER 4 | NOINHERIT 5 | NOCREATEDB 6 | NOCREATEROLE 7 | NOREPLICATION 8 | IN ROLE cwa_ppdd_user; 9 | 10 | GRANT CONNECT ON DATABASE "cwa-data" TO cwa_ppdd_analytics; 11 | GRANT USAGE ON SCHEMA data_donation TO cwa_ppdd_analytics; 12 | 13 | GRANT SELECT ON TABLE data_donation.exposure_risk_metadata, 14 | data_donation.test_result_metadata, 15 | data_donation.key_submission_metadata_with_user_metadata, 16 | data_donation.key_submission_metadata_with_client_metadata, 17 | data_donation.user_metadata, 18 | data_donation.client_metadata, 19 | data_donation.scan_instance, 20 | data_donation.exposure_window, 21 | data_donation.flyway_schema_history TO cwa_ppdd_analytics; 22 | 23 | GRANT SELECT ON TABLE data_donation.scan_instance_view TO cwa_ppdd_analytics; 24 | 25 | CREATE USER "ppdd_analytics_user" WITH INHERIT IN ROLE cwa_ppdd_analytics ENCRYPTED PASSWORD ''; 26 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/SaltService.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import app.coronawarn.datadonation.common.persistence.repository.ppac.android.SaltRepository; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class SaltService { 11 | 12 | @Autowired 13 | private SaltRepository saltRepository; 14 | 15 | Logger logger = LoggerFactory.getLogger(SaltService.class); 16 | 17 | /** 18 | * Deletes the salt provided. 19 | * 20 | * @param salt String salt 21 | */ 22 | public void deleteSalt(String salt) { 23 | try { 24 | saltRepository.deleteSalt(salt); 25 | } catch (RuntimeException ex) { 26 | throw new DeleteSaltException("Error during salt deletion.", ex); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ScanInstanceRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ScanInstance; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ScanInstanceRepository extends CrudRepository { 11 | 12 | @Query("select count(*) from scan_instance where submitted_at < :threshold") 13 | int countOlderThan(@Param("threshold") LocalDate threshold); 14 | 15 | @Modifying 16 | @Query("delete from scan_instance where submitted_at < :threshold") 17 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 18 | } 19 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/UserMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.UserMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface UserMetadataRepository extends CrudRepository { 11 | 12 | @Query("select count(*) from user_metadata where submitted_at < :threshold") 13 | int countOlderThan(@Param("threshold") LocalDate threshold); 14 | 15 | @Modifying 16 | @Query("delete from user_metadata where submitted_at < :threshold") 17 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 18 | } 19 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ClientMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ClientMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ClientMetadataRepository extends CrudRepository { 11 | 12 | @Query("select count(*) from client_metadata where submitted_at < :threshold") 13 | int countOlderThan(@Param("threshold") LocalDate threshold); 14 | 15 | @Modifying 16 | @Query("delete from client_metadata where submitted_at < :threshold") 17 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 18 | } 19 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ExposureWindowRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ExposureWindow; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ExposureWindowRepository extends CrudRepository { 11 | 12 | @Query("select count(*) from exposure_window where submitted_at < :threshold") 13 | int countOlderThan(@Param("threshold") LocalDate threshold); 14 | 15 | @Modifying 16 | @Query("delete from exposure_window where submitted_at < :threshold") 17 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 18 | } 19 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/OtpTestGenerationResponse.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import java.time.ZonedDateTime; 5 | 6 | public class OtpTestGenerationResponse { 7 | 8 | @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX") 9 | private ZonedDateTime expirationDate; 10 | 11 | private String otp; 12 | 13 | public OtpTestGenerationResponse(ZonedDateTime expirationDate, String otp) { 14 | this.expirationDate = expirationDate; 15 | this.otp = otp; 16 | } 17 | 18 | public ZonedDateTime getExpirationDate() { 19 | return expirationDate; 20 | } 21 | 22 | public void setExpirationDate(ZonedDateTime expirationDate) { 23 | this.expirationDate = expirationDate; 24 | } 25 | 26 | public String getOtp() { 27 | return otp; 28 | } 29 | 30 | public void setOtp(String otp) { 31 | this.otp = otp; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/controller/validation/ElsOneTimePasswordRequestIosValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.controller.validation; 2 | 3 | import app.coronawarn.datadonation.common.protocols.internal.ppdd.ELSOneTimePasswordRequestIOS; 4 | import app.coronawarn.datadonation.services.ppac.commons.validation.UuidConstraintValidator; 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ElsOneTimePasswordRequestIosValidator extends UuidConstraintValidator 11 | implements ConstraintValidator { 12 | 13 | @Override 14 | public boolean isValid(final ELSOneTimePasswordRequestIOS requestBody, final ConstraintValidatorContext context) { 15 | return super.isValid(requestBody.getPayload().getOtp(), context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/controller/validation/SrsOneTimePasswordRequestIosValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.controller.validation; 2 | 3 | import app.coronawarn.datadonation.common.protocols.internal.ppdd.SRSOneTimePasswordRequestIOS; 4 | import app.coronawarn.datadonation.services.ppac.commons.validation.UuidConstraintValidator; 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class SrsOneTimePasswordRequestIosValidator extends UuidConstraintValidator 11 | implements ConstraintValidator { 12 | 13 | @Override 14 | public boolean isValid(final SRSOneTimePasswordRequestIOS requestBody, final ConstraintValidatorContext context) { 15 | return super.isValid(requestBody.getPayload().getOtp(), context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/controller/validation/EdusOneTimePasswordRequestIosValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.controller.validation; 2 | 3 | import app.coronawarn.datadonation.common.protocols.internal.ppdd.EDUSOneTimePasswordRequestIOS; 4 | import app.coronawarn.datadonation.services.ppac.commons.validation.UuidConstraintValidator; 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class EdusOneTimePasswordRequestIosValidator extends UuidConstraintValidator 11 | implements ConstraintValidator { 12 | 13 | @Override 14 | public boolean isValid(final EDUSOneTimePasswordRequestIOS requestBody, final ConstraintValidatorContext context) { 15 | return super.isValid(requestBody.getPayload().getOtp(), context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/signature/ProdSignatureVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.signature; 2 | 3 | import com.google.api.client.json.webtoken.JsonWebSignature; 4 | import java.security.GeneralSecurityException; 5 | import java.security.cert.X509Certificate; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Profile("!test") 10 | @Component 11 | public class ProdSignatureVerificationStrategy implements SignatureVerificationStrategy { 12 | 13 | /** 14 | * Verify using the default JVM TrustManager that contains all Root CA certificate chains. This is 15 | * currently based on the internal implementation from the Google library used. 16 | */ 17 | @Override 18 | public X509Certificate verifySignature(JsonWebSignature jws) throws GeneralSecurityException { 19 | return jws.verifySignature(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/TestResultMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.TestResultMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface TestResultMetadataRepository extends CrudRepository { 11 | 12 | @Query("select count(*) from test_result_metadata where submitted_at < :threshold") 13 | int countOlderThan(@Param("threshold") LocalDate threshold); 14 | 15 | @Modifying 16 | @Query("delete from test_result_metadata where submitted_at < :threshold") 17 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 18 | } 19 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/apitoken/authentication/LoadTestApiTokenAuthenticationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.apitoken.authentication; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 4 | import app.coronawarn.datadonation.services.ppac.ios.client.domain.PerDeviceDataResponse; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @Profile("loadtest") 10 | public class LoadTestApiTokenAuthenticationStrategy implements ApiTokenAuthenticationStrategy { 11 | 12 | @Override 13 | public void checkApiTokenAlreadyIssued(PerDeviceDataResponse perDeviceDataResponse, 14 | boolean ignoreApiTokenAlreadyIssued) { 15 | //no implementation needed 16 | } 17 | 18 | @Override 19 | public void checkApiTokenNotAlreadyExpired(ApiTokenData apiTokenData) { 20 | //no implementation needed 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/validation/ElsOneTimePasswordRequestAndroidValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller.validation; 2 | 3 | import app.coronawarn.datadonation.common.protocols.internal.ppdd.ELSOneTimePasswordRequestAndroid; 4 | import app.coronawarn.datadonation.services.ppac.commons.validation.UuidConstraintValidator; 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ElsOneTimePasswordRequestAndroidValidator extends UuidConstraintValidator 11 | implements ConstraintValidator { 12 | 13 | @Override 14 | public boolean isValid(final ELSOneTimePasswordRequestAndroid requestBody, final ConstraintValidatorContext context) { 15 | return super.isValid(requestBody.getPayload().getOtp(), context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/validation/SrsOneTimePasswordRequestAndroidValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller.validation; 2 | 3 | import app.coronawarn.datadonation.common.protocols.internal.ppdd.SRSOneTimePasswordRequestAndroid; 4 | import app.coronawarn.datadonation.services.ppac.commons.validation.UuidConstraintValidator; 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class SrsOneTimePasswordRequestAndroidValidator extends UuidConstraintValidator 11 | implements ConstraintValidator { 12 | 13 | @Override 14 | public boolean isValid(final SRSOneTimePasswordRequestAndroid requestBody, final ConstraintValidatorContext context) { 15 | return super.isValid(requestBody.getPayload().getOtp(), context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ExposureRiskMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ExposureRiskMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ExposureRiskMetadataRepository 11 | extends CrudRepository { 12 | 13 | @Query("select count(*) from exposure_risk_metadata where submitted_at < :threshold") 14 | int countOlderThan(@Param("threshold") LocalDate threshold); 15 | 16 | @Modifying 17 | @Query("delete from exposure_risk_metadata where submitted_at < :threshold") 18 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 19 | } 20 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/validation/EdusOneTimePasswordRequestAndroidValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller.validation; 2 | 3 | import app.coronawarn.datadonation.common.protocols.internal.ppdd.EDUSOneTimePasswordRequestAndroid; 4 | import app.coronawarn.datadonation.services.ppac.commons.validation.UuidConstraintValidator; 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class EdusOneTimePasswordRequestAndroidValidator extends UuidConstraintValidator 11 | implements ConstraintValidator { 12 | 13 | @Override 14 | public boolean isValid(final EDUSOneTimePasswordRequestAndroid requestBody, 15 | final ConstraintValidatorContext context) { 16 | return super.isValid(requestBody.getPayload().getOtp(), context); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/DpkgHelper.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | /** 4 | * Helper to enable image scanning on Quay Claire. Image scanner expects /var/lib/dpkg/status instead of 5 | * /var/lib/dpkg/status.d/ Generates the file from directory content so Clair can work on it. Needs to be a java script, 6 | * as it is run on gcr.io/distroless/java:11, which has no bash. 7 | */ 8 | class DpkgHelper { 9 | 10 | public static void main(String[] args) throws IOException { 11 | File dir = new File("/var/lib/dpkg/status.d/"); 12 | PrintWriter pw = new PrintWriter("/var/lib/dpkg/status"); 13 | String[] fileNames = dir.list(); 14 | 15 | for (String fileName : fileNames) { 16 | System.out.println("Handling file: " + fileName); 17 | File f = new File(dir, fileName); 18 | BufferedReader br = new BufferedReader(new FileReader(f)); 19 | String line = br.readLine(); 20 | while (line != null) { 21 | pw.println(line); 22 | line = br.readLine(); 23 | } 24 | pw.println(); 25 | pw.flush(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ExposureWindowTestResultsRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ExposureWindowTestResult; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ExposureWindowTestResultsRepository extends CrudRepository { 11 | 12 | @Query("select count(*) from exposure_window_test_result where submitted_at < :threshold") 13 | int countOlderThan(@Param("threshold") LocalDate threshold); 14 | 15 | @Modifying 16 | @Query("delete from exposure_window_test_result where submitted_at < :threshold") 17 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 18 | } 19 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/controller/validation/ValidPpaDataRequestIosPayload.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.controller.validation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import javax.validation.Constraint; 9 | import javax.validation.Payload; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.PARAMETER) 13 | @Constraint(validatedBy = PpaDataRequestIosPayloadValidator.class) 14 | @Documented 15 | //PPADataRequestIOS 16 | public @interface ValidPpaDataRequestIosPayload { 17 | 18 | /** 19 | * Validation message. 20 | */ 21 | String message() default "Invalid submission payload for ppac."; 22 | 23 | /** 24 | * Validation groups. 25 | */ 26 | Class[] groups() default {}; 27 | 28 | /** 29 | * Payload type. 30 | */ 31 | Class[] payload() default {}; 32 | } 33 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/domain/DeviceToken.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class DeviceToken { 6 | 7 | @Id 8 | private Long id; 9 | 10 | private byte[] deviceTokenHash; 11 | 12 | Long createdAt; 13 | 14 | public DeviceToken() { 15 | } 16 | 17 | public DeviceToken(byte[] deviceTokenHash, Long createdAt) { 18 | this.deviceTokenHash = deviceTokenHash; 19 | this.createdAt = createdAt; 20 | } 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public void setId(Long id) { 27 | this.id = id; 28 | } 29 | 30 | public byte[] getDeviceTokenHash() { 31 | return deviceTokenHash; 32 | } 33 | 34 | public void setDeviceTokenHash(byte[] deviceTokenHash) { 35 | this.deviceTokenHash = deviceTokenHash; 36 | } 37 | 38 | public Long getCreatedAt() { 39 | return createdAt; 40 | } 41 | 42 | public void setCreatedAt(Long createdAt) { 43 | this.createdAt = createdAt; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F381 Feature Request" 3 | about: Do you have an idea for a new feature? 4 | labels: feature request 5 | --- 6 | 7 | 13 | 14 | ## Feature description 15 | 16 | 21 | 22 | ## Problem and motivation 23 | 24 | 28 | 29 | ## Is this something you're interested in working on 30 | 31 | 32 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ScanInstancesAtTestRegistrationRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ScanInstancesAtTestRegistration; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ScanInstancesAtTestRegistrationRepository extends 11 | CrudRepository { 12 | 13 | @Query("select count(*) from scan_instances_at_test_registration where submitted_at < :threshold") 14 | int countOlderThan(@Param("threshold") LocalDate threshold); 15 | 16 | @Modifying 17 | @Query("delete from scan_instances_at_test_registration where submitted_at < :threshold") 18 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 19 | } 20 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/android/config/AndroidServiceConfigTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.config; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | 5 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 6 | import javax.validation.ConstraintViolation; 7 | import javax.validation.Validation; 8 | import javax.validation.Validator; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | //TODO: Convert to full PPAC config test 13 | class AndroidConfigValidationTest { 14 | 15 | private Validator validator; 16 | 17 | @BeforeEach 18 | void setup() { 19 | validator = Validation.buildDefaultValidatorFactory().getValidator(); 20 | } 21 | 22 | @Test 23 | void testNonEmptyStringViolations() { 24 | PpacConfiguration.Android config = new PpacConfiguration.Android(); 25 | ConstraintViolation error = 26 | validator.validate(config).stream().findAny().orElse(null); 27 | assertNotNull(error); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/android/attestation/TimeUtilsTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation; 2 | 3 | import static app.coronawarn.datadonation.common.utils.TimeUtils.isInRange; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.time.Instant; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.ValueSource; 9 | 10 | class TimeUtilsTest { 11 | 12 | @ParameterizedTest 13 | @ValueSource(ints = {0, 1, 200, 5000, 7199, 7200}) 14 | void testWithDatesInRange(int presentOffset) { 15 | Instant present = Instant.now(); 16 | Instant upperLimit = present.plusSeconds(7200); 17 | Instant lowerLimit = present.minusSeconds(7200); 18 | 19 | Instant futureTimestamp = present.plusSeconds(presentOffset); 20 | assertTrue(isInRange(futureTimestamp.toEpochMilli(), lowerLimit, upperLimit)); 21 | 22 | Instant pastTimestamp = present.plusSeconds(presentOffset); 23 | assertTrue(isInRange(pastTimestamp.toEpochMilli(), lowerLimit, upperLimit)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/ExposureWindowsAtTestRegistrationRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.ExposureWindowsAtTestRegistration; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface ExposureWindowsAtTestRegistrationRepository extends 11 | CrudRepository { 12 | 13 | @Query("select count(*) from exposure_windows_at_test_registration where submitted_at < :threshold") 14 | int countOlderThan(@Param("threshold") LocalDate threshold); 15 | 16 | @Modifying 17 | @Query("delete from exposure_windows_at_test_registration where submitted_at < :threshold") 18 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/migration/V12__alterSerialIds.sql: -------------------------------------------------------------------------------- 1 | ALTER SEQUENCE exposure_risk_metadata_id_seq AS bigint; 2 | ALTER TABLE exposure_risk_metadata ALTER COLUMN id TYPE bigint; 3 | 4 | ALTER SEQUENCE exposure_window_id_seq AS bigint; 5 | ALTER TABLE exposure_window ALTER COLUMN id TYPE bigint; 6 | 7 | ALTER SEQUENCE test_result_metadata_id_seq AS bigint; 8 | ALTER TABLE test_result_metadata ALTER COLUMN id TYPE bigint; 9 | 10 | ALTER SEQUENCE key_submission_metadata_with_user_metadata_id_seq AS bigint; 11 | ALTER TABLE key_submission_metadata_with_user_metadata ALTER COLUMN id TYPE bigint; 12 | 13 | ALTER SEQUENCE key_submission_metadata_with_client_metadata_id_seq AS bigint; 14 | ALTER TABLE key_submission_metadata_with_client_metadata ALTER COLUMN id TYPE bigint; 15 | 16 | ALTER SEQUENCE user_metadata_id_seq AS bigint; 17 | ALTER TABLE user_metadata ALTER COLUMN id TYPE bigint; 18 | 19 | ALTER SEQUENCE client_metadata_id_seq AS bigint; 20 | ALTER TABLE client_metadata ALTER COLUMN id TYPE bigint; 21 | 22 | ALTER SEQUENCE device_token_id_seq AS bigint; 23 | ALTER TABLE device_token ALTER COLUMN id TYPE bigint; 24 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/TestProfileExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller; 2 | 3 | import static org.slf4j.LoggerFactory.getLogger; 4 | 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.context.request.WebRequest; 11 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 12 | 13 | @Profile("test") 14 | @ControllerAdvice 15 | public class TestProfileExceptionHandler extends ResponseEntityExceptionHandler { 16 | 17 | @ExceptionHandler(Exception.class) 18 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 19 | public void unknownException(final Exception ex, final WebRequest wr) { 20 | getLogger(getClass()).error("Unable to handle " + wr.getDescription(false), ex); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: codeql-analysis-workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/** 8 | pull_request: 9 | branches: 10 | - main 11 | - release/** 12 | schedule: 13 | - cron: 42 5 * * 1 14 | 15 | jobs: 16 | codeql-analysis-job: 17 | name: CodeQL Analysis 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | - name: Initialize CodeQL 23 | uses: github/codeql-action/init@v1 24 | with: 25 | languages: java 26 | queries: security-extended 27 | - name: Set up JDK 17 28 | uses: actions/setup-java@v3 29 | with: 30 | java-version: '17' 31 | distribution: temurin 32 | cache: maven 33 | - name: Build 34 | run: mvn -B clean compile -Dorg.slf4j.simpleLogger.defaultLogLevel=warn 35 | - name: Perform CodeQL Analysis 36 | uses: github/codeql-action/analyze@v1 -------------------------------------------------------------------------------- /services/els-verify/src/test/java/app/coronawarn/datadonation/services/els/otp/ElsOtpRedemtionResponseTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.els.otp; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import app.coronawarn.datadonation.common.persistence.service.OtpState; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class ElsOtpRedemtionResponseTest { 9 | 10 | @Test 11 | void modifyElsRedemptionResponseUsingSetters() { 12 | final String expValue = "eb1f6e7d-7824-421e-9810-ec7a706f9372"; 13 | ElsOtpRedemptionResponse elsOtpRedemptionResponse = new ElsOtpRedemptionResponse("eb1f6e7d-7824-421e-9810-ec7a706f9370", OtpState.VALID, true); 14 | 15 | elsOtpRedemptionResponse.setOtp(expValue); 16 | elsOtpRedemptionResponse.setState(OtpState.REDEEMED); 17 | elsOtpRedemptionResponse.setStrongClientIntegrityCheck(false); 18 | 19 | assertThat(elsOtpRedemptionResponse.getOtp()).isEqualTo(expValue); 20 | assertThat(elsOtpRedemptionResponse.getState()).isEqualTo(OtpState.REDEEMED); 21 | assertThat(elsOtpRedemptionResponse.isStrongClientIntegrityCheck()).isFalse(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /services/srs-verify/src/test/java/app/coronawarn/datadonation/services/srs/otp/SrsOtpRedemtionResponseTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.srs.otp; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import app.coronawarn.datadonation.common.persistence.service.OtpState; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class SrsOtpRedemtionResponseTest { 9 | 10 | @Test 11 | void modifySrsRedemptionResponseUsingSetters() { 12 | final String expValue = "eb1f6e7d-7824-421e-9810-ec7a706f9372"; 13 | final SrsOtpRedemptionResponse otpRedemptionResponse = new SrsOtpRedemptionResponse( 14 | "eb1f6e7d-7824-421e-9810-ec7a706f9370", OtpState.VALID, true); 15 | 16 | otpRedemptionResponse.setOtp(expValue); 17 | otpRedemptionResponse.setState(OtpState.REDEEMED); 18 | otpRedemptionResponse.setStrongClientIntegrityCheck(false); 19 | 20 | assertThat(otpRedemptionResponse.getOtp()).isEqualTo(expValue); 21 | assertThat(otpRedemptionResponse.getState()).isEqualTo(OtpState.REDEEMED); 22 | assertThat(otpRedemptionResponse.isStrongClientIntegrityCheck()).isFalse(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/scenario/ratelimit/PpacIosRateLimitStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.scenario.ratelimit; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 4 | 5 | public interface PpacIosRateLimitStrategy { 6 | 7 | /** 8 | * Check Rate Limit for EDUS Scenario. ApiToken in a EDUS Scenario can only be used once a month. 9 | * 10 | * @param apiTokenData the ApiToken that needs to be validated. 11 | */ 12 | void validateForEdus(ApiTokenData apiTokenData); 13 | 14 | /** 15 | * Check Rate Limit for PPA Scenario. ApiToken in a PPA Scenario can only be used once a day. 16 | * 17 | * @param apiTokenData the ApiToken that needs to be validated. 18 | */ 19 | void validateForPpa(ApiTokenData apiTokenData); 20 | 21 | /** 22 | * Check Rate Limit for SRS Scenario. ApiToken in a SRS Scenario can only be used once a day. 23 | * 24 | * @param apiTokenData the ApiToken that needs to be validated. 25 | */ 26 | void validateForSrs(ApiTokenData apiTokenData); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/logging/PpacLogger.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.logging; 2 | 3 | import app.coronawarn.datadonation.common.config.SecurityLogger; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class PpacLogger implements SecurityLogger { 10 | 11 | static final Logger logger = LoggerFactory.getLogger(PpacLogger.class); 12 | 13 | @Override 14 | public void error(final Exception e) { 15 | logger.error(e.getMessage(), e); 16 | } 17 | 18 | @Override 19 | public void securityWarn(final Exception e) { 20 | logger.warn(SECURITY, e.getMessage()); 21 | } 22 | 23 | void success(final String os, final String endpoint) { 24 | logger.info("Successful {} ({}) verification", os, endpoint); 25 | } 26 | 27 | @Override 28 | public void successAndroid(final String endpoint) { 29 | success("Android", endpoint); 30 | } 31 | 32 | @Override 33 | public void successIos(final String endpoint) { 34 | success("iOS", endpoint); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\u23F1\uFE0F Enhancement" 3 | about: Do you have an idea for an enhancement? 4 | labels: enhancement 5 | --- 6 | 7 | 13 | 14 | ## Current Implementation 15 | 16 | 17 | 18 | ## Suggested Enhancement 19 | 20 | 24 | 25 | ## Expected Benefits 26 | 27 | 31 | -------------------------------------------------------------------------------- /log4j2/default/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd'T'HH:mm:ssZ} %-5level %t %c{1.}[%pid]: %marker %enc{%maxLen{%m}{1023}}{CRLF} %exception{10}{separator(\u2028)} %n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/KeySubmissionMetadataWithUserMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.KeySubmissionMetadataWithUserMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface KeySubmissionMetadataWithUserMetadataRepository 11 | extends CrudRepository { 12 | 13 | @Query("SELECT COUNT(*) FROM key_submission_metadata_with_user_metadata WHERE submitted_at < :threshold") 14 | int countOlderThan(@Param("threshold") LocalDate threshold); 15 | 16 | @Modifying 17 | @Query("DELETE FROM key_submission_metadata_with_user_metadata WHERE submitted_at < :threshold") 18 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/commons/validation/UuidConstraintValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.commons.validation; 2 | 3 | import java.util.UUID; 4 | import javax.validation.ConstraintValidatorContext; 5 | 6 | public abstract class UuidConstraintValidator { 7 | 8 | private void addViolation(final ConstraintValidatorContext validatorContext) { 9 | validatorContext.buildConstraintViolationWithTemplate("OTP must be a valid UUID v4 String.") 10 | .addConstraintViolation(); 11 | } 12 | 13 | private boolean checkIsValidUuid(final String uuid, final ConstraintValidatorContext constraintValidatorContext) { 14 | boolean isUuid = false; 15 | try { 16 | UUID.fromString(uuid); 17 | isUuid = true; 18 | } catch (final IllegalArgumentException e) { 19 | addViolation(constraintValidatorContext); 20 | } 21 | return isUuid; 22 | } 23 | 24 | protected boolean isValid(final String uuid, final ConstraintValidatorContext context) { 25 | context.disableDefaultConstraintViolation(); 26 | return checkIsValidUuid(uuid, context); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/service/OtpCreationResponse.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import static java.time.format.DateTimeFormatter.ofPattern; 4 | 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import java.time.ZonedDateTime; 7 | 8 | public class OtpCreationResponse { 9 | 10 | public static final String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX"; 11 | 12 | @JsonFormat(pattern = DATE_PATTERN) 13 | private ZonedDateTime expirationDate; 14 | 15 | public OtpCreationResponse() { 16 | // empty constructor 17 | } 18 | 19 | public OtpCreationResponse(final ZonedDateTime expirationDate) { 20 | this.expirationDate = expirationDate; 21 | } 22 | 23 | public ZonedDateTime getExpirationDate() { 24 | return expirationDate; 25 | } 26 | 27 | public void setExpirationDate(final ZonedDateTime expirationDate) { 28 | this.expirationDate = expirationDate; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "{\"expirationDate\":\"" + expirationDate.format(ofPattern(DATE_PATTERN)) + "\"}"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/KeySubmissionMetadataWithClientMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.KeySubmissionMetadataWithClientMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface KeySubmissionMetadataWithClientMetadataRepository 11 | extends CrudRepository { 12 | 13 | @Query("select count(*) from key_submission_metadata_with_client_metadata where submitted_at < :threshold") 14 | int countOlderThan(@Param("threshold") LocalDate threshold); 15 | 16 | @Modifying 17 | @Query("delete from key_submission_metadata_with_client_metadata where submitted_at < :threshold") 18 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 19 | } 20 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/commons/AbstractDelayManagerTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.commons; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import app.coronawarn.datadonation.services.ppac.android.controller.AndroidDelayManager; 6 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class AbstractDelayManagerTest { 10 | 11 | @Test 12 | final void testUpdateFakeRequestDelay() { 13 | final AbstractDelayManager delayManager = new AndroidDelayManager(new PpacConfiguration() { 14 | @Override 15 | public long getFakeDelayMovingAverageSamples() { 16 | return 5L; 17 | } 18 | 19 | @Override 20 | public long getInitialFakeDelayMilliseconds() { 21 | return 100L; 22 | } 23 | }); 24 | 25 | assertEquals(0.1, delayManager.getFakeDelayInSeconds()); // 100 : 1000 = 0,1 26 | delayManager.updateFakeRequestDelay(200); // 100 + (200 - 100) : 5 = 120 27 | assertEquals(0.12, delayManager.getFakeDelayInSeconds()); // 120 : 1000 = 0,12 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/controller/validation/ValidOneTimePasswordRequestIos.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.controller.validation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import javax.validation.Constraint; 9 | import javax.validation.Payload; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.PARAMETER) 13 | @Constraint(validatedBy = { 14 | EdusOneTimePasswordRequestIosValidator.class, 15 | ElsOneTimePasswordRequestIosValidator.class, 16 | SrsOneTimePasswordRequestIosValidator.class }) 17 | @Documented 18 | public @interface ValidOneTimePasswordRequestIos { 19 | 20 | /** 21 | * Validation message. 22 | */ 23 | String message() default "Invalid payload for otp creation."; 24 | 25 | /** 26 | * Validation groups. 27 | */ 28 | Class[] groups() default {}; 29 | 30 | /** 31 | * Payload type. 32 | */ 33 | Class[] payload() default {}; 34 | } 35 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/metrics/SummarizedExposureWindowsWithUserMetadataRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.metrics.SummarizedExposureWindowsWithUserMetadata; 4 | import java.time.LocalDate; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | public interface SummarizedExposureWindowsWithUserMetadataRepository extends 11 | CrudRepository { 12 | 13 | @Query("select count(*) from summarized_exposure_windows_with_user_metadata where submitted_at < :threshold") 14 | int countOlderThan(@Param("threshold") LocalDate threshold); 15 | 16 | @Modifying 17 | @Query("delete from summarized_exposure_windows_with_user_metadata where submitted_at < :threshold") 18 | void deleteOlderThan(@Param("threshold") LocalDate threshold); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/validation/ValidAndroidOneTimePasswordRequest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller.validation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import javax.validation.Constraint; 9 | import javax.validation.Payload; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.PARAMETER) 13 | @Constraint(validatedBy = { EdusOneTimePasswordRequestAndroidValidator.class, 14 | ElsOneTimePasswordRequestAndroidValidator.class, SrsOneTimePasswordRequestAndroidValidator.class }) 15 | @Documented 16 | public @interface ValidAndroidOneTimePasswordRequest { 17 | 18 | /** 19 | * Validation message. 20 | */ 21 | String message() default "Invalid payload for OTP creation."; 22 | 23 | /** 24 | * Validation groups. 25 | */ 26 | Class[] groups() default {}; 27 | 28 | /** 29 | * Payload type. 30 | */ 31 | Class[] payload() default {}; 32 | } 33 | -------------------------------------------------------------------------------- /log4j2/default/log4j2-dev.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %xwEx 5 | %d{yyyy-MM-dd'T'HH:mm:ssZ} %-5level %t %c{1.}[%pid]: %marker %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/controller/DeleteSaltController.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.controller; 2 | 3 | import static app.coronawarn.datadonation.common.config.UrlConstants.DELETE_SALT; 4 | 5 | import app.coronawarn.datadonation.common.persistence.service.SaltService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | @Profile("test") 16 | public class DeleteSaltController { 17 | 18 | @Autowired 19 | private SaltService saltService; 20 | 21 | @DeleteMapping(value = DELETE_SALT) 22 | public ResponseEntity deleteSalt(@PathVariable("salt") String salt) { 23 | saltService.deleteSalt(salt); 24 | return new ResponseEntity<>(String.format("Salt: %s was deleted", salt), HttpStatus.OK); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/domain/AndroidId.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain; 2 | 3 | import javax.validation.constraints.Size; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | @Table("android_id") 8 | public class AndroidId { 9 | 10 | /** 11 | * Peppered Android ID. 12 | */ 13 | @Id 14 | @Size(min = 44, max = 44) 15 | private String id; 16 | 17 | Long expirationDate; 18 | 19 | Long lastUsedSrs; 20 | 21 | public Long getExpirationDate() { 22 | return expirationDate; 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | public Long getLastUsedSrs() { 30 | return lastUsedSrs; 31 | } 32 | 33 | public void setExpirationDate(final Long expirationDate) { 34 | this.expirationDate = expirationDate; 35 | } 36 | 37 | public void setId(final String id) { 38 | this.id = id; 39 | } 40 | 41 | public void setLastUsedSrs(final Long lastUsedSrs) { 42 | this.lastUsedSrs = lastUsedSrs; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return getId(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F6A8 Bug" 3 | about: Did you come across a bug or unexpected behaviour differing from the docs? 4 | labels: bug 5 | --- 6 | 7 | 13 | 14 | ## Describe the bug 15 | 16 | 17 | 18 | ## Expected behaviour 19 | 20 | 21 | 22 | ## Steps to reproduce the issue 23 | 24 | 25 | 26 | 32 | 33 | ## Technical details 34 | 35 | - Host Machine OS (Windows/Linux/Mac): 36 | 37 | ## Possible Fix 38 | 39 | 40 | 41 | ## Additional context 42 | 43 | 44 | -------------------------------------------------------------------------------- /services/ppac/src/test/resources/certificates/test.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDGjCCAgICCQD+j8wt4mdSBzANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJE 3 | RTEMMAoGA1UECgwDU0FQMRIwEAYDVQQDDAlsb2NhbGhvc3QxHjAcBgkqhkiG9w0B 4 | CQEWD2VtYWlsQGVtYWlsLmNvbTAeFw0yMTAxMjYxOTIzNTFaFw0yMTAyMjUxOTIz 5 | NTFaME8xCzAJBgNVBAYTAkRFMQwwCgYDVQQKDANTQVAxEjAQBgNVBAMMCWxvY2Fs 6 | aG9zdDEeMBwGCSqGSIb3DQEJARYPZW1haWxAZW1haWwuY29tMIIBIjANBgkqhkiG 7 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2XbljmWsgN0eeY4wwHKEH/H+igqI/Em0ErjY 8 | 8qa7Qb+PBuqXUAUE0i46Nd2cJL4bu9yeIbn0QLUKEh6KG4xaotDm6cS4kYd0qopY 9 | XHYajCdlc7ey7YvNVyy8aFzDq+UHdyz5KvCbRsg5Xamczy5ouPq6t36CWzV+flss 10 | ZWYG+DF/poAvk1pVdLzMCdY5qaBsEaUDiYiL63SszprRHsUr34ZTk4hNM/0iQ9rR 11 | Vx4uJjuG0L+w8Lrnm9Z3qZ4GpdehulgZcFXvLyHZbASKbfqdJbJM5r4K6BTAkxVO 12 | TXzS0+u1SZgUqT6WgtXmlc0xkRsyXrHNAUP8zzuq9MVKS0TpcQIDAQABMA0GCSqG 13 | SIb3DQEBCwUAA4IBAQBhjpBgPODOI0GlRlvmJpI/TVvCLJjL4/bzOQehQPaLm+0O 14 | UTKjuDcP8e8rLDjgnyNWlKjezR+9UHBMd/T+WgP6mSwVC8D9gaGk986vun/e7V0E 15 | Ad/bNLizlKFVrLhvhZBIORxcIDE+DjGLsOhw3Jl530++ex0+L53NIWGo0tS5In28 16 | KYCDHU1kjUqlbyCKyfjTTgI4dD+/hdYjsW9SNtQy+w2SZ25ybnjetuLfLi4OuD8+ 17 | T+ivypR+lbkel22ATHu64dtemJLyy9zfIZCE6BWlipZ6g3zcb3RI0c//5UaGErtj 18 | mhrdNITFRYyRqMe4WUD++f2Wt3+rnlR0ucqyRcLW 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/persistence/service/SaltServiceTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.service; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | import static org.mockito.ArgumentMatchers.any; 5 | import static org.mockito.Mockito.doThrow; 6 | 7 | import app.coronawarn.datadonation.common.persistence.repository.ppac.android.SaltRepository; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.boot.test.mock.mockito.MockBean; 12 | import org.springframework.test.annotation.DirtiesContext; 13 | import org.springframework.test.context.ActiveProfiles; 14 | 15 | @SpringBootTest 16 | @DirtiesContext 17 | @ActiveProfiles("test") 18 | class SaltServiceTest { 19 | 20 | @Autowired 21 | SaltService saltService; 22 | 23 | @MockBean 24 | SaltRepository saltRepository; 25 | 26 | @Test 27 | void testThrownRuntimeExceptionForSaltRepository() { 28 | doThrow(new RuntimeException()).when(saltRepository).deleteSalt(any()); 29 | assertThrows(DeleteSaltException.class, () -> saltService.deleteSalt("test")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/client/IosDeviceApiClient.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.client; 2 | 3 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 4 | 5 | import app.coronawarn.datadonation.services.ppac.ios.client.domain.PerDeviceDataQueryRequest; 6 | import app.coronawarn.datadonation.services.ppac.ios.client.domain.PerDeviceDataUpdateRequest; 7 | import org.springframework.cloud.openfeign.FeignClient; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestHeader; 12 | 13 | @FeignClient(name = "deviceApi", url = "${ppac.ios.device-api-url}") 14 | public interface IosDeviceApiClient { 15 | 16 | @PostMapping(value = "/query_two_bits") 17 | ResponseEntity queryDeviceData(@RequestHeader(AUTHORIZATION) final String jwt, 18 | @RequestBody PerDeviceDataQueryRequest queryRequest); 19 | 20 | @PostMapping(value = "/update_two_bits") 21 | ResponseEntity updatePerDeviceData(@RequestHeader(AUTHORIZATION) final String jwt, 22 | @RequestBody PerDeviceDataUpdateRequest updateRequest); 23 | } 24 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/config/AndroidTestBeanConfig.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.config; 2 | 3 | import app.coronawarn.datadonation.services.ppac.android.attestation.ProdSrsRateLimitVerificationStrategy; 4 | import app.coronawarn.datadonation.services.ppac.android.attestation.TestSrsRateLimitVerificationStrategy; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | @TestConfiguration 9 | public class AndroidTestBeanConfig { 10 | 11 | @Bean 12 | public PpacConfiguration ppacConfiguration() { 13 | final PpacConfiguration config = new PpacConfiguration(); 14 | config.setAndroid(new PpacConfiguration.Android()); 15 | config.getAndroid().setAndroidIdPepper("abcd"); 16 | config.setSrsTimeBetweenSubmissionsInDays(90); 17 | return config; 18 | } 19 | 20 | @Bean 21 | public ProdSrsRateLimitVerificationStrategy prodStrategy(final PpacConfiguration config) { 22 | return new ProdSrsRateLimitVerificationStrategy(config); 23 | } 24 | 25 | @Bean 26 | public TestSrsRateLimitVerificationStrategy testStrategy(final PpacConfiguration config) { 27 | return new TestSrsRateLimitVerificationStrategy(config); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/signature/TestSignatureVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.signature; 2 | 3 | import static app.coronawarn.datadonation.services.ppac.android.attestation.signature.JwsGenerationUtil.getTestCertificate; 4 | 5 | import com.google.api.client.json.webtoken.JsonWebSignature; 6 | import java.security.GeneralSecurityException; 7 | import java.security.cert.X509Certificate; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Profile("test") 12 | @Component 13 | public class TestSignatureVerificationStrategy implements SignatureVerificationStrategy { 14 | 15 | private final X509Certificate certificate; 16 | 17 | public TestSignatureVerificationStrategy() throws Exception { 18 | // Default constructor is used in tests. 19 | certificate = getTestCertificate(); 20 | } 21 | 22 | /** 23 | * Just return the configured test related certificate as if it were trusted by the platform's TrustManagers. 24 | */ 25 | @Override 26 | public X509Certificate verifySignature(final JsonWebSignature jws) throws GeneralSecurityException { 27 | return certificate; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/persistence/repository/android/SaltDataRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.android; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import app.coronawarn.datadonation.common.persistence.domain.ppac.android.SaltData; 6 | import app.coronawarn.datadonation.common.persistence.repository.ppac.android.SaltRepository; 7 | import java.time.LocalDate; 8 | import org.junit.jupiter.api.AfterEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; 12 | 13 | @DataJdbcTest 14 | class SaltDataRepositoryTest { 15 | 16 | @Autowired 17 | private SaltRepository saltRepository; 18 | 19 | @AfterEach 20 | void tearDown() { 21 | saltRepository.deleteAll(); 22 | } 23 | 24 | @Test 25 | void testSaltIsPersisted() { 26 | long epochDate = LocalDate.now().toEpochDay(); 27 | saltRepository.persist("test-salt", epochDate); 28 | SaltData saltData = saltRepository.findAll().iterator().next(); 29 | assertEquals("test-salt", saltData.getSalt()); 30 | assertEquals(saltData.getCreatedAt().longValue(), epochDate); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /services/ppac/src/main/resources/certificates/test.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUDCCAjgCCQDTgE48kvP/PjANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJE 3 | RTEQMA4GA1UECAwHR2VybWFueTEMMAoGA1UECgwDU0FQMRswGQYDVQQDDBJhdHRl 4 | c3QuYW5kcm9pZC5jb20xHjAcBgkqhkiG9w0BCQEWD2VtYWlsQGVtYWlsLmNvbTAe 5 | Fw0yMjAzMDMxMDMwNDdaFw0yMzAzMDMxMDMwNDdaMGoxCzAJBgNVBAYTAkRFMRAw 6 | DgYDVQQIDAdHZXJtYW55MQwwCgYDVQQKDANTQVAxGzAZBgNVBAMMEmF0dGVzdC5h 7 | bmRyb2lkLmNvbTEeMBwGCSqGSIb3DQEJARYPZW1haWxAZW1haWwuY29tMIIBIjAN 8 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxw321iZOkIDAYz1Fu5M15CX2Oa0j 9 | +OCI1W1RGT3T2BFsbC8Y9OrO651fv6jsupAXHC3es/pskAs4zZcuFzNYVo0ywzvm 10 | yVOSXpc0peI0Fx29KriMIITs8H3/kiPeBDP2LNjh/LnnXVSs3D9voZFOgbtEaYad 11 | AIY8CoexdQK/rCfgFFQyoeXqLvd9z4vtAGLPr6k771lPnJZGHbeDK7y7UqRWRggh 12 | i7tI4NHjxysg68pmv9A0kjpT4b93Ew2LXhuNeQMSQO0vH12ZjfyJw5LRKs1FTMG7 13 | fUckvtogmXm8hI47OOUzUejJNOl96NrAr3baKGTCmScXfikqJXredt7krQIDAQAB 14 | MA0GCSqGSIb3DQEBBQUAA4IBAQB9oJ1ppIX0EgGYs/MtZIipXMxpJ2j9wPyNyLDb 15 | EQNgWTuC0yW89TjCEC+P1aNxlXLllKOQVvMni/amV/2+1CdgMCSQRMdxHUFT9KnL 16 | F0NzpS0nRj1PbPoeMcdDsNUl3nPjjYbcvkYALOs7IdgcOj/REBBnM87RhLN46s1B 17 | zDMinuIKV59dLFQdlRKabxKmJNqucTgM3fmzZwYSCVme+aSJgGLCwMqmzSe2rTIv 18 | y2oXvycDEEnKTaoUmXtq+WNlKLCMBVEOHjrRuKaCvKN/DzZnttVZdESXZbq0r1xo 19 | fDmxaNalUzqJ0Oq12t0RZgZpsDd/dQfgLrPqH6UQxcMqPAEi 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/devicetoken/ProdDeviceTokenRedemptionStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.devicetoken; 2 | 3 | import app.coronawarn.datadonation.services.ppac.ios.verification.errors.DeviceTokenRedeemed; 4 | import app.coronawarn.datadonation.services.ppac.ios.verification.errors.InternalServerError; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.dao.DuplicateKeyException; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @Profile("!loadtest") 11 | public class ProdDeviceTokenRedemptionStrategy implements DeviceTokenRedemptionStrategy { 12 | 13 | /** 14 | * How to handle exceptions during OTP redemption. 15 | * 16 | * @param e in case of {@link DuplicateKeyException} a {@link DeviceTokenRedeemed} is thrown. 17 | * @throws InternalServerError if given {@link Exception} is not an instance of 18 | * {@link DuplicateKeyException}. 19 | */ 20 | @Override 21 | public void redeem(Exception e) throws InternalServerError { 22 | if (e.getCause() instanceof DuplicateKeyException) { 23 | throw new DeviceTokenRedeemed(e); 24 | } 25 | throw new InternalServerError(e); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/retention/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | logging: 3 | level: 4 | org: 5 | springframework: off 6 | root: off 7 | spring: 8 | flyway: 9 | enabled: true 10 | locations: classpath:/db/migration 11 | schemas: data_donation 12 | datasource: 13 | url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw 14 | hikari: 15 | schema: data_donation 16 | test: 17 | database: 18 | # Use datasource as defined above. 19 | replace: none 20 | 21 | services: 22 | retention: 23 | otp-retention-days: 1 24 | els-otp-retention-days: 2 25 | srs-otp-retention-days: 2 26 | time-between-submissions-in-days: 42 27 | exposure-risk-metadata-retention-days: 2 28 | exposure-window-retention-days: 3 29 | key-metadata-with-client-retention-days: 4 30 | key-metadata-with-user-retention-days: 5 31 | test-result-metadata-retention-days: 6 32 | api-token-retention-days: 7 33 | device-token-retention-hours: 8 34 | salt-retention-hours: 9 35 | client-metadata-retention-days: 10 36 | user-metadata-retention-days: 1 37 | summarized-exposure-window-retention-days: 14 38 | exposure-window-at-test-registration-retention-days: 14 39 | scan-instance-at-test-registration-retention-days: 14 40 | exposure-window-test-result-retention-days: 14 41 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/validation/DateInRangeValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.validation; 2 | 3 | import static org.springframework.util.ObjectUtils.isEmpty; 4 | 5 | import java.time.LocalDate; 6 | import javax.validation.ConstraintValidator; 7 | import javax.validation.ConstraintValidatorContext; 8 | 9 | public class DateInRangeValidator implements ConstraintValidator { 10 | 11 | protected LocalDate from = LocalDate.MIN; 12 | 13 | protected LocalDate till = LocalDate.MAX; 14 | 15 | @Override 16 | public void initialize(final DateInRange annotation) { 17 | if (!isEmpty(annotation.from())) { 18 | from = LocalDate.parse(annotation.from()); 19 | } 20 | if (!isEmpty(annotation.till())) { 21 | till = LocalDate.parse(annotation.till()); 22 | } 23 | 24 | assert from == null || till == null || from.isBefore(till) : "{from: " + from + "} is not before {till: " + till 25 | + "}"; 26 | } 27 | 28 | @Override 29 | public boolean isValid(final LocalDate date, final ConstraintValidatorContext context) { 30 | // null values are valid 31 | if (date == null) { 32 | return true; 33 | } 34 | 35 | return (from.isBefore(date) || from.isEqual(date)) && (till.isEqual(date) || till.isAfter(date)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /services/edus/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | edus: 3 | # True to require basicIntegrity for PPAC to pass, false otherwise. 4 | require-basic-integrity: true 5 | # True to require ctsProfileMatch for PPAC to pass, false otherwise. 6 | require-cts-profile-match: true 7 | # True to require evaluationType to contain BASIC for PPAC to pass, false otherwise. 8 | require-evaluation-type-basic: false 9 | # True to require evaluationType to contain HARDWARE_BACKED for PPAC to pass, false otherwise. 10 | require-evaluation-type-hardware-backed: true 11 | #The number of hours an OTP is valid for relative to its Created At property. (min value: 0, max value: 768 (32 x 24)) 12 | otp-validity-in-hours: 1 13 | --- 14 | 15 | server: 16 | shutdown: graceful 17 | ssl: 18 | enabled: false 19 | 20 | logging: 21 | level: 22 | org: 23 | springframework: off 24 | root: off 25 | spring: 26 | flyway: 27 | enabled: true 28 | locations: classpath:/db/migration 29 | schemas: data_donation 30 | main: 31 | banner-mode: off 32 | datasource: 33 | enabled: true 34 | url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw 35 | hikari: 36 | schema: data_donation 37 | test: 38 | database: 39 | # Use datasource as defined above. 40 | replace: none 41 | -------------------------------------------------------------------------------- /setup/setup-roles.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS data_donation; 2 | 3 | -- create roles 4 | CREATE ROLE cwa_ppdd_user 5 | NOLOGIN 6 | NOSUPERUSER 7 | NOINHERIT 8 | NOCREATEDB 9 | NOCREATEROLE 10 | NOREPLICATION; 11 | 12 | GRANT CONNECT ON DATABASE cwa TO cwa_ppdd_user; 13 | GRANT USAGE ON SCHEMA data_donation TO cwa_ppdd_user; 14 | 15 | CREATE ROLE cwa_ppdd_flyway 16 | NOSUPERUSER 17 | INHERIT 18 | NOCREATEDB 19 | NOCREATEROLE 20 | NOREPLICATION 21 | IN ROLE cwa_ppdd_user; 22 | 23 | GRANT ALL ON SCHEMA data_donation TO cwa_ppdd_flyway; 24 | 25 | CREATE ROLE cwa_ppdd_edus 26 | NOSUPERUSER 27 | INHERIT 28 | NOCREATEDB 29 | NOCREATEROLE 30 | NOREPLICATION 31 | IN ROLE cwa_ppdd_user; 32 | 33 | CREATE ROLE cwa_ppdd_ppac 34 | NOSUPERUSER 35 | INHERIT 36 | NOCREATEDB 37 | NOCREATEROLE 38 | NOREPLICATION 39 | IN ROLE cwa_ppdd_user; 40 | 41 | CREATE ROLE cwa_ppdd_retention 42 | NOSUPERUSER 43 | INHERIT 44 | NOCREATEDB 45 | NOCREATEROLE 46 | NOREPLICATION 47 | IN ROLE cwa_ppdd_user; 48 | 49 | CREATE ROLE cwa_ppdd_els_verify 50 | NOSUPERUSER 51 | INHERIT 52 | NOCREATEDB 53 | NOCREATEROLE 54 | NOREPLICATION 55 | IN ROLE cwa_ppdd_user; 56 | 57 | CREATE ROLE cwa_ppdd_srs_verify 58 | NOSUPERUSER 59 | INHERIT 60 | NOCREATEDB 61 | NOCREATEROLE 62 | NOREPLICATION 63 | IN ROLE cwa_ppdd_user; 64 | -------------------------------------------------------------------------------- /services/els-verify/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | edus: 3 | # True to require basicIntegrity for PPAC to pass, false otherwise. 4 | require-basic-integrity: true 5 | # True to require ctsProfileMatch for PPAC to pass, false otherwise. 6 | require-cts-profile-match: true 7 | # True to require evaluationType to contain BASIC for PPAC to pass, false otherwise. 8 | require-evaluation-type-basic: false 9 | # True to require evaluationType to contain HARDWARE_BACKED for PPAC to pass, false otherwise. 10 | require-evaluation-type-hardware-backed: true 11 | #The number of hours an OTP is valid for relative to its Created At property. (min value: 0, max value: 768 (32 x 24)) 12 | otp-validity-in-hours: 1 13 | --- 14 | 15 | server: 16 | shutdown: graceful 17 | ssl: 18 | enabled: false 19 | 20 | logging: 21 | level: 22 | org: 23 | springframework: off 24 | root: off 25 | spring: 26 | flyway: 27 | enabled: true 28 | locations: classpath:/db/migration 29 | schemas: data_donation 30 | main: 31 | banner-mode: off 32 | datasource: 33 | enabled: true 34 | url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw 35 | hikari: 36 | schema: data_donation 37 | test: 38 | database: 39 | # Use datasource as defined above. 40 | replace: none 41 | -------------------------------------------------------------------------------- /services/srs-verify/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | edus: 3 | # True to require basicIntegrity for PPAC to pass, false otherwise. 4 | require-basic-integrity: true 5 | # True to require ctsProfileMatch for PPAC to pass, false otherwise. 6 | require-cts-profile-match: true 7 | # True to require evaluationType to contain BASIC for PPAC to pass, false otherwise. 8 | require-evaluation-type-basic: false 9 | # True to require evaluationType to contain HARDWARE_BACKED for PPAC to pass, false otherwise. 10 | require-evaluation-type-hardware-backed: true 11 | #The number of hours an OTP is valid for relative to its Created At property. (min value: 0, max value: 768 (32 x 24)) 12 | otp-validity-in-hours: 1 13 | --- 14 | 15 | server: 16 | shutdown: graceful 17 | ssl: 18 | enabled: false 19 | 20 | logging: 21 | level: 22 | org: 23 | springframework: off 24 | root: off 25 | spring: 26 | flyway: 27 | enabled: true 28 | locations: classpath:/db/migration 29 | schemas: data_donation 30 | main: 31 | banner-mode: off 32 | datasource: 33 | enabled: true 34 | url: jdbc:tc:postgresql:11.5:///databasename?TC_TMPFS=/testtmpfs:rw 35 | hikari: 36 | schema: data_donation 37 | test: 38 | database: 39 | # Use datasource as defined above. 40 | replace: none 41 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/ppac/android/SaltRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.ppac.android; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ppac.android.SaltData; 4 | import org.springframework.data.jdbc.repository.query.Modifying; 5 | import org.springframework.data.jdbc.repository.query.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | /** 11 | * created_at time in milliseconds since epoch. 12 | */ 13 | @Repository 14 | public interface SaltRepository extends CrudRepository { 15 | 16 | @Modifying 17 | @Query("insert into salt (salt,created_at)" + "values(:salt,:createdAt)") 18 | void persist(@Param("salt") String salt, @Param("createdAt") long createdAt); 19 | 20 | @Modifying 21 | @Query("delete from salt where created_at < :threshold") 22 | void deleteOlderThan(@Param("threshold") long threshold); 23 | 24 | @Query("select count(*) from salt where created_at < :threshold") 25 | int countOlderThan(@Param("threshold") long threshold); 26 | 27 | @Modifying 28 | @Query("delete from salt where salt = :salt") 29 | void deleteSalt(@Param("salt") String salt); 30 | } 31 | -------------------------------------------------------------------------------- /common/persistence/src/main/resources/db/specific/postgresql/V4__grantPermissions.sql: -------------------------------------------------------------------------------- 1 | -- AC 2 | GRANT SELECT, INSERT, UPDATE ON TABLE 3 | data_donation.api_token, 4 | data_donation.device_token, 5 | data_donation.exposure_risk_metadata, 6 | data_donation.exposure_window, 7 | data_donation.key_submission_metadata_with_client_metadata, 8 | data_donation.key_submission_metadata_with_user_metadata, 9 | data_donation.one_time_password, 10 | data_donation.salt, 11 | data_donation.scan_instance, 12 | data_donation.test_result_metadata, 13 | data_donation.client_metadata, 14 | data_donation.user_metadata 15 | TO cwa_ppdd_ppac; 16 | 17 | GRANT ALL ON ALL SEQUENCES IN SCHEMA data_donation TO cwa_ppdd_ppac; 18 | 19 | GRANT SELECT, INSERT, UPDATE ON TABLE 20 | data_donation.one_time_password 21 | TO cwa_ppdd_edus; 22 | 23 | GRANT SELECT, DELETE ON TABLE 24 | data_donation.api_token, 25 | data_donation.device_token, 26 | data_donation.exposure_risk_metadata, 27 | data_donation.exposure_window, 28 | data_donation.key_submission_metadata_with_client_metadata, 29 | data_donation.key_submission_metadata_with_user_metadata, 30 | data_donation.one_time_password, 31 | data_donation.salt, 32 | data_donation.scan_instance, 33 | data_donation.test_result_metadata, 34 | data_donation.client_metadata, 35 | data_donation.user_metadata 36 | TO cwa_ppdd_retention; 37 | -------------------------------------------------------------------------------- /log4j2/test/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %xwEx 5 | %d{yyyy-MM-dd'T'HH:mm:ssZ} %-5level %t %c{1.}[%pid]: %marker %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /services/edus/src/main/java/app/coronawarn/datadonation/services/edus/otp/OtpRedemptionResponse.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.edus.otp; 2 | 3 | import app.coronawarn.datadonation.common.persistence.service.OtpState; 4 | 5 | public class OtpRedemptionResponse { 6 | 7 | private String otp; 8 | private OtpState state; 9 | private boolean strongClientIntegrityCheck; 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * @param otp The one time password. 15 | * @param state The OTP state. 16 | * @param strongClientIntegrityCheck The strongClientIntegrityCheck. 17 | */ 18 | public OtpRedemptionResponse(String otp, 19 | OtpState state, boolean strongClientIntegrityCheck) { 20 | this.otp = otp; 21 | this.state = state; 22 | this.strongClientIntegrityCheck = strongClientIntegrityCheck; 23 | } 24 | 25 | public String getOtp() { 26 | return otp; 27 | } 28 | 29 | public void setOtp(String otp) { 30 | this.otp = otp; 31 | } 32 | 33 | public OtpState getState() { 34 | return state; 35 | } 36 | 37 | public void setState(OtpState state) { 38 | this.state = state; 39 | } 40 | 41 | public boolean isStrongClientIntegrityCheck() { 42 | return strongClientIntegrityCheck; 43 | } 44 | 45 | public void setStrongClientIntegrityCheck(boolean strongClientIntegrityCheck) { 46 | this.strongClientIntegrityCheck = strongClientIntegrityCheck; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /services/els-verify/src/main/java/app/coronawarn/datadonation/services/els/otp/ElsOtpRedemptionResponse.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.els.otp; 2 | 3 | import app.coronawarn.datadonation.common.persistence.service.OtpState; 4 | 5 | public class ElsOtpRedemptionResponse { 6 | 7 | private String otp; 8 | private OtpState state; 9 | private boolean strongClientIntegrityCheck; 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * @param otp The els one time password . 15 | * @param state The els OTP state. 16 | * @param strongClientIntegrityCheck The strongClientIntegrityCheck. 17 | */ 18 | public ElsOtpRedemptionResponse(String otp, 19 | OtpState state, boolean strongClientIntegrityCheck) { 20 | this.otp = otp; 21 | this.state = state; 22 | this.strongClientIntegrityCheck = strongClientIntegrityCheck; 23 | } 24 | 25 | public String getOtp() { 26 | return otp; 27 | } 28 | 29 | public void setOtp(String otp) { 30 | this.otp = otp; 31 | } 32 | 33 | public OtpState getState() { 34 | return state; 35 | } 36 | 37 | public void setState(OtpState state) { 38 | this.state = state; 39 | } 40 | 41 | public boolean isStrongClientIntegrityCheck() { 42 | return strongClientIntegrityCheck; 43 | } 44 | 45 | public void setStrongClientIntegrityCheck(boolean strongClientIntegrityCheck) { 46 | this.strongClientIntegrityCheck = strongClientIntegrityCheck; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /common/protocols/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | common 7 | app.coronawarn.data 8 | ${revision} 9 | ../pom.xml 10 | 11 | 12 | protocols 13 | 14 | 15 | 16 | com.google.protobuf 17 | protobuf-java 18 | 19 | 20 | 21 | 22 | 23 | true 24 | true 25 | true 26 | 27 | 28 | 29 | 30 | 31 | maven-compiler-plugin 32 | 33 | 34 | 35 | -Xlint:none 36 | 37 | 38 | 39 | 40 | org.xolstice.maven.plugins 41 | protobuf-maven-plugin 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/android/attestation/timestamp/ProdTimestampVerificationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.android.attestation.timestamp; 2 | 3 | import static app.coronawarn.datadonation.common.utils.TimeUtils.isInRange; 4 | 5 | import app.coronawarn.datadonation.services.ppac.android.attestation.errors.FailedAttestationTimestampValidation; 6 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 7 | import java.time.Instant; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Profile("!loadtest") 13 | public final class ProdTimestampVerificationStrategy implements TimestampVerificationStrategy { 14 | 15 | private final PpacConfiguration appParameters; 16 | 17 | public ProdTimestampVerificationStrategy(PpacConfiguration config) { 18 | this.appParameters = config; 19 | } 20 | 21 | @Override 22 | public void validateTimestamp(long timestampMs) { 23 | Integer attestationValidity = appParameters.getAndroid().getAttestationValidity(); 24 | Instant present = Instant.now(); 25 | Instant upperLimit = present.plusSeconds(attestationValidity); 26 | Instant lowerLimit = present.minusSeconds(attestationValidity); 27 | if (!isInRange(timestampMs, lowerLimit, upperLimit)) { 28 | throw new FailedAttestationTimestampValidation(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/scenario/ratelimit/LoadTestPpacIosRateLimitStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.scenario.ratelimit; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 4 | import org.springframework.context.annotation.Profile; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Profile("loadtest") 9 | public class LoadTestPpacIosRateLimitStrategy implements PpacIosRateLimitStrategy { 10 | 11 | /** 12 | * Check Rate Limit for EDUS Scenario. ApiToken in a EDUS Scenario can only be used once a month. 13 | * 14 | * @param apiTokenData the ApiToken that needs to be validated. 15 | */ 16 | public void validateForEdus(ApiTokenData apiTokenData) { 17 | //no implementation needed 18 | } 19 | 20 | /** 21 | * Check Rate Limit for PPA Scenario. ApiToken in a PPA Scenario can only be used once a day. 22 | * 23 | * @param apiTokenData the ApiToken that needs to be validated. 24 | */ 25 | public void validateForPpa(ApiTokenData apiTokenData) { 26 | //no implementation needed 27 | } 28 | 29 | /** 30 | * Check Rate Limit for SRS Scenario. ApiToken in a SRS Scenario can only be used once a day. 31 | * 32 | * @param apiTokenData the ApiToken that needs to be validated. 33 | */ 34 | public void validateForSrs(ApiTokenData apiTokenData) { 35 | //no implementation needed 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /services/edus/src/main/java/app/coronawarn/datadonation/services/edus/otp/OtpControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.edus.otp; 2 | 3 | import app.coronawarn.datadonation.common.persistence.service.OtpNotFoundException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.context.request.WebRequest; 11 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 12 | 13 | @ControllerAdvice 14 | public class OtpControllerExceptionHandler extends ResponseEntityExceptionHandler { 15 | 16 | private static final Logger LOGGER = //NOSONAR logger naming 17 | LoggerFactory.getLogger(OtpControllerExceptionHandler.class); 18 | 19 | @ExceptionHandler(Exception.class) 20 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 21 | public void unknownException(Exception ex, WebRequest wr) { 22 | LOGGER.error("Unable to handle " + wr.getDescription(false), ex); 23 | } 24 | 25 | @ExceptionHandler(value = { OtpNotFoundException.class }) 26 | @ResponseStatus(HttpStatus.NOT_FOUND) 27 | public void handleNotFoundException(RuntimeException ex, WebRequest wr) { 28 | LOGGER.debug("Not found: {}", wr.getDescription(true)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /services/els-verify/src/main/java/app/coronawarn/datadonation/services/els/otp/ElsOtpControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.els.otp; 2 | 3 | import app.coronawarn.datadonation.common.persistence.service.OtpNotFoundException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.context.request.WebRequest; 11 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 12 | 13 | @ControllerAdvice 14 | public class ElsOtpControllerExceptionHandler extends ResponseEntityExceptionHandler { 15 | 16 | private static final Logger LOGGER = //NOSONAR logger naming 17 | LoggerFactory.getLogger(ElsOtpControllerExceptionHandler.class); 18 | 19 | @ExceptionHandler(Exception.class) 20 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 21 | public void unknownException(Exception ex, WebRequest wr) { 22 | LOGGER.error("Unable to handle " + wr.getDescription(false), ex); 23 | } 24 | 25 | @ExceptionHandler(value = { OtpNotFoundException.class }) 26 | @ResponseStatus(HttpStatus.NOT_FOUND) 27 | public void handleNotFoundException(RuntimeException ex, WebRequest wr) { 28 | LOGGER.debug("Not found: {}", wr.getDescription(true)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /services/srs-verify/src/main/java/app/coronawarn/datadonation/services/srs/otp/SrsOtpControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.srs.otp; 2 | 3 | import app.coronawarn.datadonation.common.persistence.service.OtpNotFoundException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.context.request.WebRequest; 11 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 12 | 13 | @ControllerAdvice 14 | public class SrsOtpControllerExceptionHandler extends ResponseEntityExceptionHandler { 15 | 16 | private static final Logger LOGGER = //NOSONAR logger naming 17 | LoggerFactory.getLogger(SrsOtpControllerExceptionHandler.class); 18 | 19 | @ExceptionHandler(Exception.class) 20 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 21 | public void unknownException(Exception ex, WebRequest wr) { 22 | LOGGER.error("Unable to handle " + wr.getDescription(false), ex); 23 | } 24 | 25 | @ExceptionHandler(value = { OtpNotFoundException.class }) 26 | @ResponseStatus(HttpStatus.NOT_FOUND) 27 | public void handleNotFoundException(RuntimeException ex, WebRequest wr) { 28 | LOGGER.debug("Not found: {}", wr.getDescription(true)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/validation/ObjectWithDateMember.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.validation; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class ObjectWithDateMember { 6 | 7 | /** 8 | * from = "1970-01-01", till = "2000-01-01". 9 | */ 10 | @DateInRange(from = "1970-01-01", till = "2000-01-01") 11 | LocalDate dateToBeValidated; 12 | 13 | /** 14 | * from = "", till = "". 15 | */ 16 | @DateInRange 17 | LocalDate dateToBeValidatedNull; 18 | 19 | /** 20 | * from = "1970-01-01", till = "". 21 | */ 22 | @DateInRange(from = "1970-01-01", message = "Date must be after {from}") 23 | LocalDate dateToBeValidatedFrom; 24 | 25 | /** 26 | * from = "", till = "2000-01-01". 27 | */ 28 | @DateInRange(till = "2000-01-01", message = "Date must be before {till}") 29 | LocalDate dateToBeValidatedTill; 30 | 31 | public void setDateToBeValidated(final LocalDate dateToBeValidated) { 32 | this.dateToBeValidated = dateToBeValidated; 33 | } 34 | 35 | public void setDateToBeValidatedFrom(final LocalDate dateToBeValidatedFrom) { 36 | this.dateToBeValidatedFrom = dateToBeValidatedFrom; 37 | } 38 | 39 | public void setDateToBeValidatedNull(final LocalDate dateToBeValidatedNull) { 40 | this.dateToBeValidatedNull = dateToBeValidatedNull; 41 | } 42 | 43 | public void setDateToBeValidatedTill(final LocalDate dateToBeValidatedTill) { 44 | this.dateToBeValidatedTill = dateToBeValidatedTill; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/persistence/domain/metrics/TechnicalMetadataTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain.metrics; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 5 | 6 | import java.time.LocalDate; 7 | import java.time.ZoneId; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class TechnicalMetadataTest { 11 | 12 | @Test 13 | void testHashCode() { 14 | TechnicalMetadata fixture = new TechnicalMetadata(null, null, null, null, null); 15 | assertEquals(28629151, fixture.hashCode()); 16 | } 17 | 18 | @Test 19 | void testEqualsObject() { 20 | LocalDate date = LocalDate.now(ZoneId.of("UTC")); 21 | TechnicalMetadata fixture = new TechnicalMetadata(date, true, true, true, true); 22 | assertEquals(fixture, fixture); 23 | assertEquals(fixture, new TechnicalMetadata(date, true, true, true, true)); 24 | 25 | assertNotEquals(null, fixture); 26 | assertNotEquals(new Object(), fixture); 27 | assertNotEquals(new TechnicalMetadata(date, true, true, true, false), fixture); 28 | assertNotEquals(new TechnicalMetadata(date, true, true, false, true), fixture); 29 | assertNotEquals(new TechnicalMetadata(date, true, false, true, true), fixture); 30 | assertNotEquals(new TechnicalMetadata(date, false, true, true, true), fixture); 31 | assertNotEquals(new TechnicalMetadata(LocalDate.ofEpochDay(42L), true, true, true, true), fixture); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/apitoken/authentication/ProdApiTokenAuthenticationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.apitoken.authentication; 2 | 3 | import static app.coronawarn.datadonation.common.utils.TimeUtils.getLocalDateFor; 4 | import static app.coronawarn.datadonation.common.utils.TimeUtils.getLocalDateForNow; 5 | 6 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 7 | import app.coronawarn.datadonation.services.ppac.ios.client.domain.PerDeviceDataResponse; 8 | import app.coronawarn.datadonation.services.ppac.ios.verification.errors.ApiTokenExpired; 9 | import java.time.LocalDate; 10 | import org.springframework.context.annotation.Profile; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | @Profile("!test && !loadtest") 15 | public class ProdApiTokenAuthenticationStrategy implements ApiTokenAuthenticationStrategy { 16 | 17 | @Override 18 | public void checkApiTokenAlreadyIssued(PerDeviceDataResponse perDeviceDataResponse, 19 | boolean ignoreApiTokenAlreadyIssued) { 20 | perDeviceDataResponse.getLastUpdated().ifPresent(this::validateApiTokenNotAlreadyUsed); 21 | } 22 | 23 | @Override 24 | public void checkApiTokenNotAlreadyExpired(ApiTokenData apiTokenData) { 25 | LocalDate expirationDate = getLocalDateFor(apiTokenData.getExpirationDate()); 26 | LocalDate now = getLocalDateForNow(); 27 | if (now.isAfter(expirationDate)) { 28 | throw new ApiTokenExpired(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/DeviceTokenRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.DeviceToken; 4 | import java.util.Optional; 5 | import org.springframework.data.jdbc.repository.query.Modifying; 6 | import org.springframework.data.jdbc.repository.query.Query; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.data.repository.query.Param; 9 | import org.springframework.stereotype.Repository; 10 | 11 | /** 12 | * created_at time in milliseconds since epoch. 13 | */ 14 | @Repository 15 | public interface DeviceTokenRepository extends CrudRepository { 16 | 17 | @Query("SELECT * FROM device_token d WHERE d.device_token_hash = :deviceTokenHash ") 18 | Optional findByDeviceTokenHash(@Param("deviceTokenHash") byte[] deviceToken); 19 | 20 | @Modifying 21 | @Query("delete from device_token where created_at < :threshold") 22 | void deleteOlderThan(@Param("threshold") long threshold); 23 | 24 | @Query("select count(*) from device_token where created_at < :threshold") 25 | int countOlderThan(@Param("threshold") long threshold); 26 | 27 | @Modifying 28 | @Query("insert into device_token (id,device_token_hash,created_at)" + "values(:id,:deviceTokenHash,:createdAt)") 29 | void persist(@Param("id") Long id, @Param("deviceTokenHash") byte[] deviceTokenHash, 30 | @Param("createdAt") long createdAt); 31 | } 32 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/client/domain/PerDeviceDataResponse.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.client.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import java.util.Optional; 6 | 7 | public class PerDeviceDataResponse { 8 | 9 | Boolean bit0; 10 | Boolean bit1; 11 | @JsonProperty("last_update_time") 12 | String lastUpdated; // YYYY-MM 13 | 14 | public PerDeviceDataResponse() { 15 | // empty constructor 16 | } 17 | 18 | /** 19 | * Create new instance of per-device data. 20 | * 21 | * @param bit0 first of a total of 2 bits. 22 | * @param bit1 second of a total of 2 bits. 23 | * @param lastUpdated when this per-device data was updated the last time. 24 | */ 25 | public PerDeviceDataResponse(boolean bit0, boolean bit1, String lastUpdated) { 26 | this.bit0 = bit0; 27 | this.bit1 = bit1; 28 | this.lastUpdated = lastUpdated; 29 | } 30 | 31 | public boolean isBit0() { 32 | return bit0; 33 | } 34 | 35 | public void setBit0(boolean bit0) { 36 | this.bit0 = bit0; 37 | } 38 | 39 | public boolean isBit1() { 40 | return bit1; 41 | } 42 | 43 | public void setBit1(boolean bit1) { 44 | this.bit1 = bit1; 45 | } 46 | 47 | @JsonIgnore 48 | public Optional getLastUpdated() { 49 | return Optional.ofNullable(lastUpdated); 50 | } 51 | 52 | public void setLastUpdated(String lastUpdated) { 53 | this.lastUpdated = lastUpdated; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/OneTimePasswordRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.OneTimePassword; 4 | import org.springframework.data.jdbc.repository.query.Modifying; 5 | import org.springframework.data.jdbc.repository.query.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | /** 11 | * created_at time in seconds since epoch. 12 | */ 13 | @Repository 14 | public interface OneTimePasswordRepository extends CrudRepository { 15 | 16 | @Modifying 17 | @Query("delete from one_time_password where expiration_timestamp < :threshold or redemption_timestamp < :threshold") 18 | void deleteOlderThan(@Param("threshold") long threshold); 19 | 20 | @Query("select count(*) from one_time_password where expiration_timestamp < :threshold " 21 | + "or redemption_timestamp < :threshold") 22 | int countOlderThan(@Param("threshold") long threshold); 23 | 24 | @Modifying 25 | @Query("insert into one_time_password (password, redemption_timestamp, expiration_timestamp) " 26 | + "values(:password, :redemptionTimestamp, :expirationTimestamp)") 27 | void insert(@Param("password") String password, 28 | @Param("redemptionTimestamp") Long redemptionTimestamp, 29 | @Param("expirationTimestamp") Long expirationTimestamp 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/devicedata/LoadtestPerDeviceDataValidator.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.devicedata; 2 | 3 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 4 | import app.coronawarn.datadonation.services.ppac.ios.client.IosDeviceApiClient; 5 | import app.coronawarn.datadonation.services.ppac.ios.verification.JwtProvider; 6 | import app.coronawarn.datadonation.services.ppac.ios.verification.devicetoken.DeviceTokenService; 7 | import feign.FeignException; 8 | import feign.FeignException.BadRequest; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.context.annotation.Profile; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | @Profile("loadtest") 16 | public class LoadtestPerDeviceDataValidator extends PerDeviceDataValidator { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(LoadtestPerDeviceDataValidator.class); 19 | 20 | public LoadtestPerDeviceDataValidator(IosDeviceApiClient iosDeviceApiClient, JwtProvider jwtProvider, 21 | DeviceTokenService deviceTokenService, PpacConfiguration ppacConfiguration) { 22 | super(iosDeviceApiClient, jwtProvider, deviceTokenService, ppacConfiguration); 23 | } 24 | 25 | @Override 26 | protected void treatGeneralRequestError(FeignException e) { 27 | LOGGER.debug(e.getMessage(), e); 28 | } 29 | 30 | @Override 31 | protected void treatBadRequest(BadRequest e) { 32 | LOGGER.debug(e.getMessage(), e); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/domain/metrics/DataDonationMetric.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain.metrics; 2 | 3 | import java.util.Objects; 4 | import java.util.Set; 5 | import javax.validation.ConstraintViolation; 6 | import javax.validation.Validation; 7 | import javax.validation.Validator; 8 | import org.springframework.data.annotation.Id; 9 | 10 | /** 11 | * Acts as the base class with common features for entities that capture Data Donation information. 12 | */ 13 | public abstract class DataDonationMetric { 14 | 15 | private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); 16 | 17 | @Id 18 | protected final Long id; 19 | 20 | protected DataDonationMetric(Long id) { 21 | this.id = id; 22 | } 23 | 24 | public Long getId() { 25 | return id; 26 | } 27 | 28 | /** 29 | * Performs a validation of constraints defined in subclasses. 30 | * 31 | * @return A set of constraint violations of this entity. 32 | */ 33 | public Set> validate() { 34 | return VALIDATOR.validate(this); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(id); 40 | } 41 | 42 | @Override 43 | public boolean equals(Object obj) { 44 | if (this == obj) { 45 | return true; 46 | } 47 | if (obj == null || getClass() != obj.getClass()) { 48 | return false; 49 | } 50 | DataDonationMetric other = (DataDonationMetric) obj; 51 | return Objects.equals(id, other.id); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/SrsOneTimePasswordRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.SrsOneTimePassword; 4 | import org.springframework.data.jdbc.repository.query.Modifying; 5 | import org.springframework.data.jdbc.repository.query.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | /** 11 | * created_at time in seconds since epoch. 12 | */ 13 | @Repository 14 | public interface SrsOneTimePasswordRepository extends CrudRepository { 15 | 16 | @Query("SELECT COUNT(*) FROM srs_one_time_password WHERE expiration_timestamp < :threshold " 17 | + "OR redemption_timestamp < :threshold") 18 | int countOlderThan(@Param("threshold") long threshold); 19 | 20 | @Modifying 21 | @Query("DELETE FROM srs_one_time_password WHERE expiration_timestamp < :threshold " 22 | + "OR redemption_timestamp < :threshold") 23 | void deleteOlderThan(@Param("threshold") long threshold); 24 | 25 | @Modifying 26 | @Query("INSERT INTO srs_one_time_password (password, redemption_timestamp, expiration_timestamp) " 27 | + "VALUES(:password, :redemptionTimestamp, :expirationTimestamp)") 28 | void insert(@Param("password") String password, 29 | @Param("redemptionTimestamp") Long redemptionTimestamp, 30 | @Param("expirationTimestamp") Long expirationTimestamp); 31 | } 32 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/ios/apitoken/ApiTokenDataBuilderTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.apitoken; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 6 | import app.coronawarn.datadonation.common.utils.TimeUtils; 7 | import app.coronawarn.datadonation.services.ppac.ios.verification.apitoken.ApiTokenBuilder; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.mockito.junit.jupiter.MockitoExtension; 11 | 12 | @ExtendWith(MockitoExtension.class) 13 | class ApiTokenDataBuilderTest { 14 | 15 | @Test 16 | void buildApiToken() { 17 | String apiToken = "apitoken"; 18 | 19 | final Long now = TimeUtils.getEpochSecondsForNow(); 20 | 21 | final ApiTokenData newApiTokenData = ApiTokenBuilder.newBuilder() 22 | .setApiToken(apiToken) 23 | .setCreatedAt(now) 24 | .setExpirationDate(now) 25 | .setLastUsedEdus(now) 26 | .setLastUsedPpac(now).build(); 27 | 28 | assertThat(newApiTokenData.getLastUsedEdus()).isPresent(); 29 | assertThat(newApiTokenData.getLastUsedPpac()).isPresent(); 30 | 31 | assertThat(newApiTokenData.getLastUsedPpac().get()).isEqualTo(now); 32 | assertThat(newApiTokenData.getLastUsedEdus().get()).isEqualTo(now); 33 | assertThat(newApiTokenData.getExpirationDate()).isEqualTo(now); 34 | assertThat(newApiTokenData.getCreatedAt()).isEqualTo(now); 35 | assertThat(newApiTokenData.getApiToken()).isEqualTo(apiToken); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/apitoken/authentication/TestApiTokenAuthenticationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.apitoken.authentication; 2 | 3 | import static app.coronawarn.datadonation.common.utils.TimeUtils.getLocalDateFor; 4 | import static app.coronawarn.datadonation.common.utils.TimeUtils.getLocalDateForNow; 5 | 6 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 7 | import app.coronawarn.datadonation.services.ppac.ios.client.domain.PerDeviceDataResponse; 8 | import app.coronawarn.datadonation.services.ppac.ios.verification.errors.ApiTokenExpired; 9 | import java.time.LocalDate; 10 | import org.springframework.context.annotation.Profile; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | @Profile("test") 15 | public class TestApiTokenAuthenticationStrategy implements ApiTokenAuthenticationStrategy { 16 | 17 | @Override 18 | public void checkApiTokenAlreadyIssued(PerDeviceDataResponse perDeviceDataResponse, 19 | boolean ignoreApiTokenAlreadyIssued) { 20 | if (!ignoreApiTokenAlreadyIssued) { 21 | perDeviceDataResponse.getLastUpdated() 22 | .ifPresent(this::validateApiTokenNotAlreadyUsed); 23 | } 24 | } 25 | 26 | @Override 27 | public void checkApiTokenNotAlreadyExpired(ApiTokenData apiTokenData) { 28 | LocalDate expirationDate = getLocalDateFor(apiTokenData.getExpirationDate()); 29 | LocalDate now = getLocalDateForNow(); 30 | if (now.isAfter(expirationDate)) { 31 | throw new ApiTokenExpired(); 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/ElsOneTimePasswordRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ElsOneTimePassword; 4 | import org.springframework.data.jdbc.repository.query.Modifying; 5 | import org.springframework.data.jdbc.repository.query.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | /** 11 | * created_at time in seconds since epoch. 12 | */ 13 | @Repository 14 | public interface ElsOneTimePasswordRepository extends CrudRepository { 15 | 16 | @Modifying 17 | @Query("delete from els_one_time_password where expiration_timestamp < :threshold " 18 | + "or redemption_timestamp < :threshold") 19 | void deleteOlderThan(@Param("threshold") long threshold); 20 | 21 | @Query("select count(*) from els_one_time_password where expiration_timestamp < :threshold " 22 | + "or redemption_timestamp < :threshold") 23 | int countOlderThan(@Param("threshold") long threshold); 24 | 25 | @Modifying 26 | @Query("insert into els_one_time_password (password, redemption_timestamp, expiration_timestamp) " 27 | + "values(:password, :redemptionTimestamp, :expirationTimestamp)") 28 | void insert(@Param("password") String password, 29 | @Param("redemptionTimestamp") Long redemptionTimestamp, 30 | @Param("expirationTimestamp") Long expirationTimestamp 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/persistence/domain/metrics/ScanInstanceTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.domain.metrics; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.LocalDate; 6 | import java.time.ZoneId; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class ScanInstanceTest { 11 | 12 | @Test 13 | void testScanInstanceEquals() { 14 | LocalDate date = LocalDate.now(ZoneId.of("UTC")); 15 | TechnicalMetadata technicalMetadata = new TechnicalMetadata(date, true, false, true, false); 16 | ScanInstance fixture = new ScanInstance(2L, 2, 42, 42, 42, technicalMetadata); 17 | assertEquals(fixture, fixture); 18 | 19 | Assertions.assertNotEquals(fixture, 20 | new ScanInstance(2L, 2, 42, 42, 42, null)); 21 | Assertions.assertNotEquals(fixture, 22 | new ScanInstance(2L, 2, 42, 42, 2, technicalMetadata)); 23 | Assertions.assertNotEquals(fixture, 24 | new ScanInstance(2L, 2, 42, 2, 42, technicalMetadata)); 25 | Assertions.assertNotEquals(fixture, 26 | new ScanInstance(2L, 2, 2, 42, 42, technicalMetadata)); 27 | Assertions.assertNotEquals(fixture, 28 | new ScanInstance(2L, 1, 42, 42, 42, technicalMetadata)); 29 | Assertions.assertNotEquals(fixture, 30 | new ScanInstance(1L, 2, 42, 42, 42, technicalMetadata)); 31 | } 32 | 33 | @Test 34 | void testHashCode() { 35 | ScanInstance fixture = new ScanInstance(null, null, null, null, null, null); 36 | assertEquals(887503681, fixture.hashCode()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/apitoken/ApiTokenBuilder.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.apitoken; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 4 | 5 | public class ApiTokenBuilder { 6 | 7 | public static ApiTokenBuilder newBuilder() { 8 | return new ApiTokenBuilder(); 9 | } 10 | 11 | private String apiToken; 12 | private Long createdAt; 13 | private Long expirationDate; 14 | private Long lastUsedEdus; 15 | private Long lastUsedPpac; 16 | private Long lastUsedSrs; 17 | 18 | public ApiTokenData build() { 19 | return new ApiTokenData(apiToken, expirationDate, createdAt, lastUsedEdus, lastUsedPpac, lastUsedSrs); 20 | } 21 | 22 | public ApiTokenBuilder setApiToken(final String apiToken) { 23 | this.apiToken = apiToken; 24 | return this; 25 | } 26 | 27 | public ApiTokenBuilder setCreatedAt(final Long createdAt) { 28 | this.createdAt = createdAt; 29 | return this; 30 | } 31 | 32 | public ApiTokenBuilder setExpirationDate(final Long expirationDate) { 33 | this.expirationDate = expirationDate; 34 | return this; 35 | } 36 | 37 | public ApiTokenBuilder setLastUsedEdus(final Long lastUsedEdus) { 38 | this.lastUsedEdus = lastUsedEdus; 39 | return this; 40 | } 41 | 42 | public ApiTokenBuilder setLastUsedPpac(final Long lastUsedPpac) { 43 | this.lastUsedPpac = lastUsedPpac; 44 | return this; 45 | } 46 | 47 | public ApiTokenBuilder setLastUsedSrs(final Long lastUsedSrs) { 48 | this.lastUsedSrs = lastUsedSrs; 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/validation/DateInRange.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.validation; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | import java.time.LocalDate; 15 | import javax.validation.Constraint; 16 | import javax.validation.Payload; 17 | 18 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 19 | @Retention(RUNTIME) 20 | @Documented 21 | @Constraint(validatedBy = { DateInRangeValidator.class }) 22 | public @interface DateInRange { 23 | 24 | /** 25 | * Groups where potential violations should be kept together. 26 | */ 27 | Class[] groups() default { }; 28 | 29 | /** 30 | * Payload. 31 | */ 32 | Class[] payload() default { }; 33 | 34 | /** 35 | * Date must be between {from} and {till}. 36 | */ 37 | String message() default "Date must be between {from} and {till}"; 38 | 39 | /** 40 | * Default if not set: {@link LocalDate#MIN}. 41 | */ 42 | String from() default ""; 43 | 44 | /** 45 | * Default if not set: {@link LocalDate#MAX}. 46 | */ 47 | String till() default ""; 48 | } 49 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/apitoken/authentication/ApiTokenAuthenticationStrategy.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.apitoken.authentication; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.ApiTokenData; 4 | import app.coronawarn.datadonation.services.ppac.ios.client.domain.PerDeviceDataResponse; 5 | import app.coronawarn.datadonation.services.ppac.ios.verification.errors.ApiTokenAlreadyUsed; 6 | import java.time.YearMonth; 7 | import java.time.ZoneOffset; 8 | import java.time.format.DateTimeFormatter; 9 | 10 | public interface ApiTokenAuthenticationStrategy { 11 | 12 | void checkApiTokenAlreadyIssued(PerDeviceDataResponse perDeviceDataResponse, 13 | boolean ignoreApiTokenAlreadyIssued); 14 | 15 | void checkApiTokenNotAlreadyExpired(ApiTokenData apiTokenData); 16 | 17 | /** 18 | * Default implementation to check whether the provided per-device Data was updated this Month. If so then this 19 | * methods throws {@link ApiTokenAlreadyUsed}. 20 | * 21 | * @param perDeviceDataLastUpdated the per-device Data to check against (Format yyyy-MM). 22 | * @throws ApiTokenAlreadyUsed if the provided per-device data was already used this month. 23 | */ 24 | default void validateApiTokenNotAlreadyUsed(String perDeviceDataLastUpdated) { 25 | final YearMonth lastUpdated = YearMonth.parse( 26 | perDeviceDataLastUpdated, 27 | DateTimeFormatter.ofPattern("yyyy-MM")); 28 | if (YearMonth.now(ZoneOffset.UTC).equals(lastUpdated)) { 29 | throw new ApiTokenAlreadyUsed(perDeviceDataLastUpdated); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/persistence/src/main/java/app/coronawarn/datadonation/common/persistence/repository/AndroidIdRepository.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository; 2 | 3 | import app.coronawarn.datadonation.common.persistence.domain.AndroidId; 4 | import org.springframework.data.jdbc.repository.query.Modifying; 5 | import org.springframework.data.jdbc.repository.query.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | /** 11 | * created_at time in seconds since epoch. 12 | */ 13 | @Repository 14 | public interface AndroidIdRepository extends CrudRepository { 15 | 16 | @Modifying 17 | @Query("INSERT INTO android_id (id, expiration_date, last_used_srs) VALUES (:id, :expirationDate, :lastUsedSRS)") 18 | void insert(@Param("id") final String id, 19 | @Param("expirationDate") final Long expirationDate, 20 | @Param("lastUsedSRS") final Long lastUsedSrs); 21 | 22 | @Modifying 23 | @Query("UPDATE android_id SET expiration_date = :expirationDate, last_used_srs = :lastUsedSRS WHERE id = :id") 24 | void update(@Param("id") final String id, 25 | @Param("expirationDate") final Long expirationDate, 26 | @Param("lastUsedSRS") final Long lastUsedSrs); 27 | 28 | @Query("SELECT COUNT(*) FROM android_id WHERE expiration_date < :threshold") 29 | int countOlderThan(@Param("threshold") long threshold); 30 | 31 | @Modifying 32 | @Query("DELETE FROM android_id WHERE expiration_date < :threshold") 33 | void deleteOlderThan(@Param("threshold") long threshold); 34 | } 35 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/commons/AbstractDelayManager.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.commons; 2 | 3 | import app.coronawarn.datadonation.services.ppac.config.PpacConfiguration; 4 | import org.apache.commons.math3.distribution.PoissonDistribution; 5 | 6 | /** 7 | * {@link AbstractDelayManager} instances manage the response delay in the processing of fake (or "dummy") requests. 8 | */ 9 | public abstract class AbstractDelayManager { 10 | 11 | private final long movingAverageSampleSize; 12 | 13 | private long fakeDelay; 14 | 15 | protected AbstractDelayManager(final PpacConfiguration config) { 16 | fakeDelay = config.getInitialFakeDelayMilliseconds(); 17 | movingAverageSampleSize = config.getFakeDelayMovingAverageSamples(); 18 | } 19 | 20 | /** 21 | * Returns the current fake delay in seconds. Used for monitoring. 22 | * 23 | * @return fake delay in seconds 24 | */ 25 | public Double getFakeDelayInSeconds() { 26 | return fakeDelay / 1000.; 27 | } 28 | 29 | /** 30 | * Returns the current fake delay after applying random jitter. 31 | * 32 | * @return the fake delay 33 | */ 34 | public long getJitteredFakeDelay() { 35 | return new PoissonDistribution(fakeDelay).sample(); 36 | } 37 | 38 | /** 39 | * Updates the moving average for the request duration with the specified value. 40 | * 41 | * @param realRequestDuration the request duration 42 | */ 43 | public long updateFakeRequestDelay(final long realRequestDuration) { 44 | fakeDelay = fakeDelay + (realRequestDuration - fakeDelay) / movingAverageSampleSize; 45 | return fakeDelay; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /services/ppac/src/test/java/app/coronawarn/datadonation/services/ppac/ios/client/domain/PerDeviceDataResponseTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.client.domain; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.mockito.junit.jupiter.MockitoExtension; 10 | import org.springframework.test.context.junit.jupiter.SpringExtension; 11 | 12 | @ExtendWith({SpringExtension.class, MockitoExtension.class}) 13 | class PerDeviceDataResponseTest { 14 | 15 | @Test 16 | void testWriteObjectAsJsonString() throws JsonProcessingException { 17 | String value = "{\"bit0\":false,\"bit1\":true,\"last_update_time\":\"2000-12\"}"; 18 | 19 | PerDeviceDataResponse data = new PerDeviceDataResponse(false, 20 | true, "2000-12"); 21 | ObjectMapper mapper = new ObjectMapper(); 22 | String requestJson = mapper.writeValueAsString(data); 23 | 24 | assertThat(requestJson).isEqualTo(value); 25 | } 26 | 27 | @Test 28 | void testReadObjectFromJsonString() throws JsonProcessingException { 29 | String value = "{\"bit0\":false,\"bit1\":true,\"last_update_time\":\"2000-12\"}"; 30 | 31 | PerDeviceDataResponse expected = new PerDeviceDataResponse(false, 32 | true, "2000-12"); 33 | ObjectMapper mapper = new ObjectMapper(); 34 | PerDeviceDataResponse actual = mapper.readValue(value, PerDeviceDataResponse.class); 35 | 36 | assertThat(actual).usingRecursiveComparison().isEqualTo(expected); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/verification/apitoken/LoadTestApiTokenService.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.verification.apitoken; 2 | 3 | import app.coronawarn.datadonation.common.persistence.repository.ApiTokenRepository; 4 | import app.coronawarn.datadonation.services.ppac.ios.client.IosDeviceApiClient; 5 | import app.coronawarn.datadonation.services.ppac.ios.verification.JwtProvider; 6 | import app.coronawarn.datadonation.services.ppac.ios.verification.PpacIosScenarioRepository; 7 | import app.coronawarn.datadonation.services.ppac.ios.verification.apitoken.authentication.ApiTokenAuthenticationStrategy; 8 | import app.coronawarn.datadonation.services.ppac.ios.verification.scenario.ratelimit.PpacIosRateLimitStrategy; 9 | import feign.FeignException; 10 | import org.springframework.context.annotation.Profile; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | @Profile("loadtest") 15 | public class LoadTestApiTokenService extends ApiTokenService { 16 | 17 | public LoadTestApiTokenService(ApiTokenRepository apiTokenRepository, 18 | IosDeviceApiClient iosDeviceApiClient, JwtProvider jwtProvider, 19 | ApiTokenAuthenticationStrategy apiTokenAuthenticationStrategy, 20 | PpacIosRateLimitStrategy iosScenarioValidator, 21 | PpacIosScenarioRepository ppacIosScenarioRepository) { 22 | super(apiTokenRepository, iosDeviceApiClient, jwtProvider, apiTokenAuthenticationStrategy, 23 | iosScenarioValidator, ppacIosScenarioRepository); 24 | } 25 | 26 | @Override 27 | protected void treatApiClientErrors(FeignException e) { 28 | // do nothing during load tests 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/client/domain/PerDeviceValidationRequest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.client.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class PerDeviceValidationRequest { 6 | 7 | @JsonProperty("device_token") 8 | String deviceToken; 9 | @JsonProperty("transaction_id") 10 | String transactionId; 11 | Long timestamp; 12 | 13 | public PerDeviceValidationRequest() { 14 | //empty constructor 15 | } 16 | 17 | /** 18 | * Create a new instance of an validation request that can be used to verify a device against the Apple Device API. 19 | * 20 | * @param deviceToken the device token used for validation. 21 | * @param transactionId a valid transaction id for this request. 22 | * @param timestamp a valid timestamp for this request. 23 | */ 24 | public PerDeviceValidationRequest(String deviceToken, String transactionId, Long timestamp) { 25 | this.deviceToken = deviceToken; 26 | this.transactionId = transactionId; 27 | this.timestamp = timestamp; 28 | } 29 | 30 | public String getDeviceToken() { 31 | return deviceToken; 32 | } 33 | 34 | public void setDeviceToken(String deviceToken) { 35 | this.deviceToken = deviceToken; 36 | } 37 | 38 | public String getTransactionId() { 39 | return transactionId; 40 | } 41 | 42 | public void setTransactionId(String transactionId) { 43 | this.transactionId = transactionId; 44 | } 45 | 46 | public Long getTimestamp() { 47 | return timestamp; 48 | } 49 | 50 | public void setTimestamp(Long timestamp) { 51 | this.timestamp = timestamp; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /services/ppac/src/main/java/app/coronawarn/datadonation/services/ppac/ios/client/domain/PerDeviceDataQueryRequest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.services.ppac.ios.client.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class PerDeviceDataQueryRequest { 6 | 7 | @JsonProperty("device_token") 8 | private String deviceToken; 9 | 10 | @JsonProperty("transaction_id") 11 | private String transactionId; 12 | private Long timestamp; 13 | 14 | public PerDeviceDataQueryRequest() { 15 | // empty constructor 16 | } 17 | 18 | /** 19 | * Create a new instance of an query request to retrieve per-device data for a given device token. 20 | * 21 | * @param deviceToken the device token as identification. 22 | * @param transactionId a valid transaction id for this request. 23 | * @param timestamp a valid timestamp for this request. 24 | */ 25 | public PerDeviceDataQueryRequest(String deviceToken, String transactionId, Long timestamp) { 26 | this.deviceToken = deviceToken; 27 | this.transactionId = transactionId; 28 | this.timestamp = timestamp; 29 | } 30 | 31 | public String getDeviceToken() { 32 | return deviceToken; 33 | } 34 | 35 | public void setDeviceToken(String deviceToken) { 36 | this.deviceToken = deviceToken; 37 | } 38 | 39 | public String getTransactionId() { 40 | return transactionId; 41 | } 42 | 43 | public void setTransactionId(String transactionId) { 44 | this.transactionId = transactionId; 45 | } 46 | 47 | public Long getTimestamp() { 48 | return timestamp; 49 | } 50 | 51 | public void setTimestamp(Long timestamp) { 52 | this.timestamp = timestamp; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common/persistence/src/test/java/app/coronawarn/datadonation/common/persistence/repository/metrics/UserMetadataRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package app.coronawarn.datadonation.common.persistence.repository.metrics; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import app.coronawarn.datadonation.common.persistence.domain.metrics.TechnicalMetadata; 7 | import app.coronawarn.datadonation.common.persistence.domain.metrics.UserMetadata; 8 | import app.coronawarn.datadonation.common.persistence.domain.metrics.embeddable.UserMetadataDetails; 9 | import java.time.LocalDate; 10 | import org.junit.jupiter.api.AfterEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; 14 | 15 | @DataJdbcTest 16 | class UserMetadataRepositoryTest { 17 | 18 | @Autowired 19 | private UserMetadataRepository userMetadataRepository; 20 | 21 | @AfterEach 22 | void tearDown() { 23 | userMetadataRepository.deleteAll(); 24 | } 25 | 26 | @Test 27 | void userMetadataShouldBePersistedCorrectly() { 28 | UserMetadata userMetadata = 29 | new UserMetadata(null, new UserMetadataDetails(1, 2, 2), 30 | new TechnicalMetadata(LocalDate.now(), true, true, false, false)); 31 | 32 | userMetadataRepository.save(userMetadata); 33 | UserMetadata loadedEntity = userMetadataRepository.findAll().iterator().next(); 34 | assertEquals(loadedEntity.getUserMetadataDetails(), userMetadata.getUserMetadataDetails()); 35 | assertEquals(loadedEntity.getTechnicalMetadata(), userMetadata.getTechnicalMetadata()); 36 | assertNotNull(loadedEntity.getId()); 37 | } 38 | } 39 | --------------------------------------------------------------------------------