├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 01_bug.md │ ├── 02_feature_request.md │ ├── 03_enhancement.md │ ├── 04_question.md │ ├── 05_other.md │ └── config.yml └── workflows │ ├── ci-dockerfile.yml │ ├── ci-master.yml │ ├── ci-pull-request.yml │ └── ci-release-notes.yml ├── .gitignore ├── .grenrc.js ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── THIRD-PARTY-NOTICES ├── api-docs.json ├── codestyle └── checkstyle.xml ├── docs ├── architecture-overview.md ├── architecture_overview.svg └── sw_design_verification.svg ├── pom.xml ├── scripts └── Dpkg.java ├── src ├── main │ ├── java │ │ └── app │ │ │ └── coronawarn │ │ │ └── verification │ │ │ ├── VerificationApplication.java │ │ │ ├── client │ │ │ ├── IamClient.java │ │ │ ├── TestResultServerClient.java │ │ │ └── TestResultServerClientConfig.java │ │ │ ├── config │ │ │ ├── LocalSecurityConfig.java │ │ │ ├── MtlsSecurityConfig.java │ │ │ ├── OpenApiConfig.java │ │ │ ├── RequestSizeLimitFilter.java │ │ │ └── VerificationApplicationConfig.java │ │ │ ├── controller │ │ │ ├── ExternalTanController.java │ │ │ ├── ExternalTestStateController.java │ │ │ ├── ExternalTokenController.java │ │ │ ├── InternalTanController.java │ │ │ ├── InternalTestStateController.java │ │ │ └── VerificationExceptionHandler.java │ │ │ ├── domain │ │ │ ├── VerificationAppSession.java │ │ │ └── VerificationTan.java │ │ │ ├── exception │ │ │ └── VerificationServerException.java │ │ │ ├── model │ │ │ ├── AppSessionSourceOfTrust.java │ │ │ ├── AuthorizationRole.java │ │ │ ├── AuthorizationToken.java │ │ │ ├── Certs.java │ │ │ ├── HashedGuid.java │ │ │ ├── InternalTestResult.java │ │ │ ├── Key.java │ │ │ ├── LabTestResult.java │ │ │ ├── RegistrationToken.java │ │ │ ├── RegistrationTokenKeyType.java │ │ │ ├── RegistrationTokenRequest.java │ │ │ ├── Tan.java │ │ │ ├── TanSourceOfTrust.java │ │ │ ├── TanType.java │ │ │ ├── TeleTan.java │ │ │ ├── TeleTanType.java │ │ │ └── TestResult.java │ │ │ ├── repository │ │ │ ├── VerificationAppSessionRepository.java │ │ │ └── VerificationTanRepository.java │ │ │ ├── service │ │ │ ├── AppSessionService.java │ │ │ ├── EntitiesCleanupService.java │ │ │ ├── FakeDelayService.java │ │ │ ├── FakeRequestService.java │ │ │ ├── HashingService.java │ │ │ ├── JwtService.java │ │ │ ├── TanService.java │ │ │ └── TestResultServerService.java │ │ │ └── validator │ │ │ ├── RegistrationTokenKeyConstraint.java │ │ │ └── RegistrationTokenRequestValidator.java │ └── resources │ │ ├── application-cloud.yml │ │ ├── application-external.yml │ │ ├── application-internal.yml │ │ ├── application-local.yml │ │ ├── application.yml │ │ ├── bootstrap-cloud.yaml │ │ ├── bootstrap.yaml │ │ └── db │ │ ├── changelog.yml │ │ └── changelog │ │ ├── v000-create-app-session-table.yml │ │ ├── v000-create-tan-table.yml │ │ ├── v001-add-dob-hash-column.yml │ │ ├── v002-add-teletan-type-column.yml │ │ ├── v003-add-unique-hashed-guid.yml │ │ ├── v004-add-unique-registration-token-teletan.yml │ │ ├── v005-add-shedlock.yml │ │ ├── v006-add-index-dob-hashed-guid.yml │ │ └── v007-add-seperate-unique-constraints-for-hashed-guid.yml └── test │ └── java │ └── app │ └── coronawarn │ └── verification │ ├── TestUtils.java │ ├── VerificationApplicationExternalTest.java │ ├── VerificationApplicationExternalTestHttp.java │ ├── VerificationApplicationExternalTestWorkaround.java │ ├── VerificationApplicationInternalTest.java │ └── service │ ├── EntitiesCleanupServiceTest.java │ ├── HashingServiceTest.java │ ├── JwtServiceTest.java │ ├── TanServiceTest.java │ └── TestResultServerServiceTest.java └── trusted.key.gpg /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.java text diff=java 4 | *.gradle text diff=groovy 5 | *.gradle.kts text diff=groovy 6 | *.css text diff=css 7 | *.df text 8 | *.htm text diff=html 9 | *.html text diff=html 10 | *.js text 11 | *.jsp text 12 | *.jspf text 13 | *.jspx text 14 | *.properties text 15 | *.tld text 16 | *.tag text 17 | *.tagx text 18 | *.xml text 19 | 20 | *.class binary 21 | *.dll binary 22 | *.ear binary 23 | *.jar binary 24 | *.so binary 25 | *.war binary 26 | *.jks binary 27 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/workflows/ci-dockerfile.yml: -------------------------------------------------------------------------------- 1 | name: ci-dockerfile 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - Dockerfile 9 | pull_request: 10 | types: 11 | - opened 12 | - synchronize 13 | - reopened 14 | paths: 15 | - Dockerfile 16 | jobs: 17 | lint: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - run: docker pull hadolint/hadolint 22 | - run: docker run --rm --interactive hadolint/hadolint < Dockerfile 23 | -------------------------------------------------------------------------------- /.github/workflows/ci-master.yml: -------------------------------------------------------------------------------- 1 | name: ci-master 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: sigstore/cosign-installer@main 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - uses: actions/cache@v1 16 | env: 17 | cache-name: m2 18 | with: 19 | path: ~/.m2/repository 20 | key: ${{ env.cache-name }}-${{ hashFiles('**/pom.xml') }} 21 | restore-keys: ${{ env.cache-name }}- 22 | - uses: actions/setup-java@v1 23 | with: 24 | java-version: 17 25 | - name: environment 26 | run: | 27 | sudo apt-get install --yes --no-install-recommends libxml-xpath-perl 28 | export ARTIFACT_ID=$(xpath -q -e "/project/artifactId/text()" pom.xml) 29 | echo "ARTIFACT_ID=${ARTIFACT_ID}" >> $GITHUB_ENV 30 | export VERSION=$(xpath -q -e "/project/version/text()" pom.xml) 31 | export VERSION=${VERSION//-SNAPSHOT}-$(git rev-parse --short ${GITHUB_SHA}) 32 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 33 | - name: mvn version 34 | run: mvn --batch-mode versions:set -DgenerateBackupPoms=false -DnewVersion=${VERSION} 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | - name: mvn deploy 38 | run: mvn --batch-mode deploy 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | - name: mvn sonar 42 | run: | 43 | mvn --batch-mode verify sonar:sonar \ 44 | -Dsonar.login=${SONAR_TOKEN} \ 45 | -Dsonar.host.url=${SONAR_URL} \ 46 | -Dsonar.organization=${GITHUB_REPOSITORY_OWNER} \ 47 | -Dsonar.projectKey=${GITHUB_REPOSITORY/\//_} 48 | env: 49 | SONAR_URL: https://sonarcloud.io 50 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | - uses: actions/upload-artifact@v1 53 | with: 54 | name: target 55 | path: target 56 | - name: docker build 57 | run: | 58 | docker build \ 59 | --tag docker.pkg.github.com/${GITHUB_REPOSITORY}/${ARTIFACT_ID}:${VERSION} \ 60 | --tag ${MTR_REPOSITORY}/cwa-verification-server:${VERSION} \ 61 | . 62 | env: 63 | MTR_REPOSITORY: ${{ secrets.MTR_REPOSITORY }} 64 | - name: docker push github 65 | run: | 66 | echo ${GITHUB_TOKEN} | docker login docker.pkg.github.com -u ${GITHUB_REPOSITORY_OWNER} --password-stdin 67 | docker push docker.pkg.github.com/${GITHUB_REPOSITORY}/${ARTIFACT_ID}:${VERSION} 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | - name: docker push mtr 71 | run: | 72 | echo ${MTR_TOKEN} | docker login ${MTR_REPOSITORY} -u ${MTR_USER} --password-stdin 73 | docker push ${MTR_REPOSITORY}/cwa-verification-server:${VERSION} 74 | cosign sign --key env://MTR_PRIVATE_KEY ${MTR_REPOSITORY}/cwa-verification-server:${VERSION} 75 | env: 76 | MTR_REPOSITORY: ${{ secrets.MTR_REPOSITORY }} 77 | MTR_USER: ${{ secrets.MTR_USER }} 78 | MTR_TOKEN: ${{ secrets.MTR_TOKEN }} 79 | MTR_PRIVATE_KEY: ${{ secrets.MTR_PRIVATE_KEY }} 80 | COSIGN_PASSWORD: ${{ secrets.MTR_PRIVATE_KEY_PASSWORD }} 81 | -------------------------------------------------------------------------------- /.github/workflows/ci-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: ci-pull-request 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - synchronize 7 | - reopened 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - uses: actions/cache@v1 16 | env: 17 | cache-name: m2 18 | with: 19 | path: ~/.m2/repository 20 | key: ${{ env.cache-name }}-${{ hashFiles('**/pom.xml') }} 21 | restore-keys: ${{ env.cache-name }}- 22 | - uses: actions/setup-java@v1 23 | with: 24 | java-version: 17 25 | - name: mvn package 26 | run: mvn --batch-mode package 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - name: docker build 30 | run: docker build . 31 | -------------------------------------------------------------------------------- /.github/workflows/ci-release-notes.yml: -------------------------------------------------------------------------------- 1 | name: ci-release-notes 2 | on: 3 | release: 4 | types: 5 | - created 6 | - edited 7 | - prereleased 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: release notes 14 | run: | 15 | TAG=${GITHUB_REF/refs\/tags\/} 16 | npx github-release-notes release --override --tags ${TAG} 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/** 4 | !**/src/test/** 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | nbactions.txt 29 | nbactions.xml 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | /nb-configuration.xml 35 | /nbproject/ 36 | -------------------------------------------------------------------------------- /.grenrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "dataSource": "prs", 3 | "prefix": "", 4 | "onlyMilestones": false, 5 | "groupBy": { 6 | "Enhancements": [ 7 | "enhancement", 8 | "internal", 9 | "feat", 10 | "feature", 11 | "code improvement" 12 | ], 13 | "Bug Fixes": [ 14 | "fix", 15 | "bug" 16 | ], 17 | "Documentation": [ 18 | "doc", 19 | "documentation" 20 | ], 21 | "Others": [ 22 | "other", 23 | "chore" 24 | ] 25 | }, 26 | "ignoreIssuesWith": [ 27 | "wontfix", 28 | "duplicate" 29 | ], 30 | "changelogFilename": "CHANGELOG.md", 31 | "template": { 32 | commit: ({ message, url, author, name }) => `- [${message}](${url}) - ${author ? `@${author}` : name}`, 33 | issue: "- {{name}} [{{text}}]({{url}})", 34 | noLabel: "other", 35 | group: "\n#### {{heading}}\n", 36 | changelogTitle: "# Changelog\n\n", 37 | release: "## {{release}} ({{date}})\n{{body}}", 38 | releaseSeparator: "\n---\n\n" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.2.2 (05/08/2020) 4 | 5 | #### Enhancements 6 | 7 | - feat: changed Responsepadding to only appear if fake header exists [#197](https://github.com/corona-warn-app/cwa-verification-server/pull/197) 8 | 9 | #### Bug Fixes 10 | 11 | - fix: added json ignore if null [#198](https://github.com/corona-warn-app/cwa-verification-server/pull/198) 12 | - fix: used corret fake delay [#200](https://github.com/corona-warn-app/cwa-verification-server/pull/200) 13 | - fix: added delay for all endpoints [#201](https://github.com/corona-warn-app/cwa-verification-server/pull/201) 14 | 15 | --- 16 | 17 | ## v1.2.1 (27/07/2020) 18 | 19 | #### Bug Fixes 20 | 21 | - fix: temporary remove response padding [#194](https://github.com/corona-warn-app/cwa-verification-server/pull/194) 22 | - fix: update openAPI docs to meet v1.2.0 [#192](https://github.com/corona-warn-app/cwa-verification-server/pull/192) 23 | 24 | #### Others 25 | 26 | - doc: documentation of feature 'fake header' [#191](https://github.com/corona-warn-app/cwa-verification-server/pull/191) 27 | 28 | --- 29 | 30 | ## v1.2.0 (22/07/2020) 31 | 32 | #### Enhancements 33 | 34 | - feat: feature plausible deniability (aka fake header) [#188](https://github.com/corona-warn-app/cwa-verification-server/pull/188) 35 | - feat: added rate limiting for TeleTan creation [#172](https://github.com/corona-warn-app/cwa-verification-server/pull/172) 36 | - feat: split internal and external endpoints [#184](https://github.com/corona-warn-app/cwa-verification-server/pull/184) 37 | 38 | #### Bug Fixes 39 | 40 | - fix: add a simple filter which prevents BSI issue #55 (request size limitation) [#91](https://github.com/corona-warn-app/cwa-verification-server/pull/91) 41 | 42 | #### Documentation 43 | 44 | - doc: rate limit, teleTAN crypto spec [#187](https://github.com/corona-warn-app/cwa-verification-server/pull/187) 45 | 46 | --- 47 | 48 | ## v1.1.0 (07/07/2020) 49 | 50 | #### Enhancements 51 | 52 | - feat: improve logs for cdc and increase spring version [#178](https://github.com/corona-warn-app/cwa-verification-server/pull/178) 53 | - feat: automatic release notes generation [#175](https://github.com/corona-warn-app/cwa-verification-server/pull/175) 54 | - feat: custom validator for Registration Token Request [#169](https://github.com/corona-warn-app/cwa-verification-server/pull/169) 55 | - feat: use license-maven-plugin to allow generation of license file headers [#103](https://github.com/corona-warn-app/cwa-verification-server/pull/103) 56 | - feat: speed up unit tests [#167](https://github.com/corona-warn-app/cwa-verification-server/pull/167) 57 | - feat: improvement-logs-cdc [#177](https://github.com/corona-warn-app/cwa-verification-server/pull/177) 58 | - feat: improve logs regarding cdc [#155](https://github.com/corona-warn-app/cwa-verification-server/pull/155) 59 | - feat: removed unnecessary complexity [#165](https://github.com/corona-warn-app/cwa-verification-server/pull/165) 60 | 61 | #### Bug Fixes 62 | 63 | - fix: fix typos in architecture overview [#182](https://github.com/corona-warn-app/cwa-verification-server/pull/182) 64 | - fix: added Glen to codeowners, deleted DockerfilePaaS [#174](https://github.com/corona-warn-app/cwa-verification-server/pull/174) 65 | - fix: minor code quality improvements [#160](https://github.com/corona-warn-app/cwa-verification-server/pull/160) 66 | - fix: readme.md [#181](https://github.com/corona-warn-app/cwa-verification-server/pull/181) 67 | - fix: updated the release to 1.0.1 in POM-File [#171](https://github.com/corona-warn-app/cwa-verification-server/pull/171) 68 | - fix: enable container scanning of distroless container [#173](https://github.com/corona-warn-app/cwa-verification-server/pull/173) 69 | - fix: integration-JWT [#157](https://github.com/corona-warn-app/cwa-verification-server/pull/157) 70 | 71 | #### Documentation 72 | 73 | - doc: typo in API description [#170](https://github.com/corona-warn-app/cwa-verification-server/pull/170) 74 | - doc: corrected teleTAN specification in architecture-overview.md [#159](https://github.com/corona-warn-app/cwa-verification-server/pull/159) 75 | - doc: update architecture-overview.md [#180](https://github.com/corona-warn-app/cwa-verification-server/pull/180) 76 | - doc: update contact mail in documents [#176](https://github.com/corona-warn-app/cwa-verification-server/pull/176) 77 | - doc: reference 'Create Registration Token for E06.01' [#41](https://github.com/corona-warn-app/cwa-verification-server/pull/41) 78 | - doc: update figures to match solution architecture [#116](https://github.com/corona-warn-app/cwa-verification-server/pull/116) 79 | 80 | --- 81 | 82 | ## v1.0.0 (12/06/2020) 83 | 84 | #### Documentation 85 | 86 | - Update README [#156](https://github.com/corona-warn-app/cwa-verification-server/pull/156) 87 | 88 | --- 89 | 90 | ## v0.6.1 (09/06/2020) 91 | Release candidate 92 | * mTLS support fix 93 | 94 | --- 95 | 96 | ## 0.6.0 (08/06/2020) 97 | Release Candidate 98 | --- 99 | 100 | ## fix uppercase teleTan (01/06/2020) 101 | 102 | --- 103 | 104 | ## Beta API fix (31/05/2020) 105 | 106 | --- 107 | 108 | ## v0.5.1-beta (31/05/2020) 109 | v0.5.1-beta fo cwa-verification-server 110 | --- 111 | 112 | ## v0.3.1-alpha (22/05/2020) 113 | Initial public alpha release. -------------------------------------------------------------------------------- /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-verification-team 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [cwa-opensource@telekom.de](mailto:cwa-opensource@telekom.de). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | 131 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Code of conduct 4 | 5 | All members of the project community must abide by the [Contributor Covenant, version 2.0](CODE_OF_CONDUCT.md). 6 | Only by respecting each other can we develop a productive, collaborative community. 7 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting [cwa-opensource@telekom.de](mailto:cwa-opensource@telekom.de) and/or a project maintainer. 8 | 9 | We appreciate your courtesy of avoiding political questions here. Issues which are not related to the project itself will be closed by our community managers. 10 | 11 | ## Engaging in our project 12 | 13 | We use GitHub to manage reviews of pull requests. 14 | 15 | * If you are a new contributor, see: [Steps to Contribute](#steps-to-contribute) 16 | 17 | * If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) a suitable maintainer of this repository (see [CODEOWNERS](CODEOWNERS) of the repository you want to contribute to) in the description of the pull request. 18 | 19 | * If you plan to do something more involved, please reach out to us and send an [email](mailto:cwa-opensource@telekom.de). This will avoid unnecessary work and surely give you and us a good deal of inspiration. 20 | 21 | * Relevant coding style guidelines are available in the respective sub-repositories as they are programming language-dependent. 22 | 23 | ## Steps to Contribute 24 | 25 | Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on. This is to prevent duplicated efforts from other contributors on the same issue. 26 | 27 | If you have questions about one of the issues, please comment on them, and one of the maintainers will clarify. 28 | 29 | We kindly ask you to follow the [Pull Request Checklist](#Pull-Request-Checklist) to ensure reviews can happen accordingly. 30 | 31 | ## Contributing Code 32 | 33 | You are welcome to contribute code in order to fix a bug or to implement a new feature. 34 | 35 | The following rule governs code contributions: 36 | 37 | * Contributions must be licensed under the [Apache 2.0 License](LICENSE) 38 | * Newly created files must be opened by a *license file header* which can be generated with the command `mvn clean license:update-file-header`. 39 | * At least if you add a new file to the repository, add your name into the contributor section of the file NOTICE (please respect the preset entry structure) 40 | 41 | ## Contributing Documentation 42 | 43 | You are welcome to contribute documentation to the project. 44 | 45 | The following rule governs documentation contributions: 46 | 47 | * Contributions must be licensed under the same license as code, the [Apache 2.0 License](LICENSE) 48 | 49 | ## Pull Request Checklist 50 | 51 | * Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase your changes. 52 | 53 | * Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). 54 | 55 | * Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration tests. If tested manually, provide information about the test scope in the PR description (e.g. “Test passed: Upgrade version from 0.42 to 0.42.23.”). 56 | 57 | * Create _Work In Progress [WIP]_ pull requests only if you need clarification or an explicit review before you can continue your work item. 58 | 59 | * If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review by contacting us via [email](mailto:opensource@telekom.de). 60 | 61 | * Post review: 62 | * If a review requires you to change your commit(s), please test the changes again. 63 | * Amend the affected commit(s) and force push onto your branch. 64 | * Set respective comments in your GitHub review to resolved. 65 | * Create a general PR comment to notify the reviewers that your amendments are ready for another round of review. 66 | 67 | ## Issues and Planning 68 | 69 | * We use GitHub issues to track bugs and enhancement requests. 70 | 71 | * Please provide as much context as possible when you open an issue. The information you provide must be comprehensive enough to reproduce that issue for the assignee. Therefore, contributors may use but aren't restricted to the issue template provided by the project maintainers. 72 | 73 | * When creating an issue, try using one of our issue templates which already contain some guidelines on which content is expected to process the issue most efficiently. If no template applies, you can of course also create an issue from scratch. 74 | 75 | * Please apply one or more applicable [labels](https://github.com/corona-warn-app/cwa-documentation/labels) to your issue so that all community members are able to cluster the issues better. 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/java17-debian11:latest 2 | WORKDIR / 3 | COPY target/*.jar app.jar 4 | COPY scripts/Dpkg.java Dpkg.java 5 | RUN ["java", "Dpkg.java"] 6 | USER 65534:65534 7 | CMD ["app.jar"] 8 | EXPOSE 8080 9 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | @Library('sbs-jenkinslib') _ 3 | 4 | /* 5 | * T-Systems SBS pipeline build see below link for internal (sorry) 6 | * documentation details. 7 | * https://sbs.t-systems.com/wiki/Job+Type:+Jenkins+Pipeline+Build 8 | */ 9 | sbsBuild( 10 | jdk: 'jdk11', 11 | dockerAlternateRegistries: [ 12 | 'MTR_SBS@mtr.external.otc.telekomcloud.com/sbs/cwa-verification-server' 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Deutsche Telekom AG. 2 | 3 | This project is licensed under Apache License, Version 2.0; 4 | you may not use them except in compliance with the License. 5 | 6 | Contributors: 7 | ------------- 8 | 9 | Karsten Reincke [kreincke], Deutsche Telekom AG 10 | Daniel Eder [daniel-eder], T-Mobile International Austria GmbH 11 | Dominik Fischer [dfischer-tech], T-Systems International GmbH 12 | Julien Hagestedt [jhagestedt], T-Systems International GmbH 13 | Maximilian Laue [mlaue-tech], T-Systems International GmbH 14 | Andreas Scheibal [ascheibal], T-Systems International GmbH 15 | Michael Schulte [mschulte-tsi], T-Systems International GmbH 16 | Lars Stelzner [lstelzne-tech], T-Systems International GmbH 17 | Andreas Mandel [amandel], T-Systems International GmbH 18 | Martin Scheffler [martinschefflerTSI] T-Systems International GmbH 19 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | The Corona-Warn-App is built with security and data privacy in mind to ensure your data is safe. We are grateful for security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline. 4 | 5 | **Please do not report security vulnerabilities directly on GitHub. GitHub Issues can be publicly seen and therefore would result in a direct disclosure.** 6 | 7 | * Please address questions about data privacy, security concepts, and other media requests to the cert@telekom.de mailbox. 8 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | app.coronawarn.verification 6 | cwa-verification-server 7 | 1.5.9-SNAPSHOT 8 | jar 9 | 10 | cwa-verification-server 11 | CWA verification server project. 12 | 13 | T-Systems International GmbH 14 | 15 | 16 | https://www.coronawarn.app/ 17 | 18 | https://github.com/corona-warn-app/cwa-verification-server/actions?query=workflow%3Aci 19 | 20 | 21 | https://github.com/corona-warn-app/cwa-verification-server/issues 22 | 23 | 24 | https://github.com/corona-warn-app/cwa-verification-server 25 | 26 | 27 | 28 | app.coronawarn 29 | cwa-parent 30 | 2.0.2 31 | 32 | 33 | 34 | Corona-Warn-App / cwa-verification 35 | 2020 36 | apache_v2 37 | 38 | **/VerificationApplication.java, 39 | **/model/*, 40 | **/domain/*, 41 | **/config/*, 42 | **/exception/* 43 | 44 | 45 | 46 | 47 | 48 | github 49 | https://maven.pkg.github.com/corona-warn-app/cwa-verification-server 50 | 51 | 52 | 53 | 54 | 55 | github 56 | https://maven.pkg.github.com/corona-warn-app/cwa-verification-server 57 | 58 | 59 | 60 | 61 | 62 | app.coronawarn 63 | cwa-parent-spring-boot 64 | ${project.parent.version} 65 | pom 66 | 67 | 68 | app.coronawarn 69 | cwa-parent-feign 70 | ${project.parent.version} 71 | pom 72 | 73 | 74 | app.coronawarn 75 | cwa-parent-psql-persistence 76 | ${project.parent.version} 77 | pom 78 | 79 | 80 | app.coronawarn 81 | cwa-parent-shedlock 82 | ${project.parent.version} 83 | pom 84 | 85 | 86 | app.coronawarn 87 | cwa-parent-validation 88 | ${project.parent.version} 89 | pom 90 | 91 | 92 | 93 | commons-io 94 | commons-io 95 | 96 | 97 | commons-codec 98 | commons-codec 99 | 100 | 101 | org.apache.commons 102 | commons-math3 103 | compile 104 | 105 | 106 | 107 | io.jsonwebtoken 108 | jjwt-api 109 | 110 | 111 | io.jsonwebtoken 112 | jjwt-impl 113 | 114 | 115 | io.jsonwebtoken 116 | jjwt-jackson 117 | runtime 118 | 119 | 120 | 121 | io.micrometer 122 | micrometer-core 123 | 124 | 125 | io.micrometer 126 | micrometer-registry-prometheus 127 | 128 | 129 | 130 | 131 | 132 | 133 | org.springframework.boot 134 | spring-boot-maven-plugin 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-checkstyle-plugin 140 | 141 | 142 | 143 | org.jacoco 144 | jacoco-maven-plugin 145 | 146 | 147 | 148 | org.codehaus.mojo 149 | license-maven-plugin 150 | 151 | 152 | 153 | org.sonarsource.scanner.maven 154 | sonar-maven-plugin 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /scripts/Dpkg.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | class Dpkg { 4 | public static void main(String[] args) throws IOException { 5 | File dir = new File("/var/lib/dpkg/status.d/"); 6 | PrintWriter pw = new PrintWriter("/var/lib/dpkg/status"); 7 | String[] fileNames = dir.list(); 8 | for (String fileName : fileNames) { 9 | System.out.println("Handling file: " + fileName); 10 | File f = new File(dir, fileName); 11 | BufferedReader br = new BufferedReader(new FileReader(f)); 12 | String line = br.readLine(); 13 | while (line != null) { 14 | pw.println(line); 15 | line = br.readLine(); 16 | } 17 | pw.println(); 18 | pw.flush(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/VerificationApplication.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification; 22 | 23 | import app.coronawarn.verification.config.VerificationApplicationConfig; 24 | import org.springframework.boot.SpringApplication; 25 | import org.springframework.boot.autoconfigure.SpringBootApplication; 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 27 | import org.springframework.cloud.openfeign.EnableFeignClients; 28 | import org.springframework.scheduling.annotation.EnableScheduling; 29 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 30 | 31 | /** 32 | * The Application class. 33 | */ 34 | @EnableFeignClients 35 | @SpringBootApplication 36 | @EnableWebSecurity 37 | @EnableScheduling 38 | @EnableConfigurationProperties({VerificationApplicationConfig.class}) 39 | public class VerificationApplication { 40 | 41 | /** 42 | * The main Method. 43 | * 44 | * @param args the args for the main method 45 | */ 46 | public static void main(String[] args) { 47 | SpringApplication.run(VerificationApplication.class, args); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/client/IamClient.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.client; 22 | 23 | import app.coronawarn.verification.model.Certs; 24 | import org.springframework.cloud.openfeign.FeignClient; 25 | import org.springframework.http.MediaType; 26 | import org.springframework.web.bind.annotation.GetMapping; 27 | 28 | /** 29 | * This class represents the IAM feign client. 30 | */ 31 | @FeignClient(name = "IamService", url = "${jwt.server}") 32 | public interface IamClient { 33 | /** 34 | * This method gets the cert information from the IAM Server. 35 | * @return Testresult from server 36 | */ 37 | @GetMapping(value = "/auth/realms/cwa/protocol/openid-connect/certs", 38 | consumes = MediaType.APPLICATION_JSON_VALUE 39 | ) 40 | Certs certs(); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/client/TestResultServerClient.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.client; 22 | 23 | import app.coronawarn.verification.model.HashedGuid; 24 | import app.coronawarn.verification.model.TestResult; 25 | import org.springframework.cloud.openfeign.FeignClient; 26 | import org.springframework.http.MediaType; 27 | import org.springframework.web.bind.annotation.PostMapping; 28 | 29 | /** 30 | * This class represents the Labor Server service feign client. 31 | */ 32 | @FeignClient( 33 | name = "testResultServerClient", 34 | url = "${cwa-testresult-server.url}", 35 | configuration = TestResultServerClientConfig.class) 36 | public interface TestResultServerClient { 37 | 38 | /** 39 | * This method gets a testResult from the LabServer. 40 | * 41 | * @param guid for TestResult 42 | * @return TestResult from server 43 | */ 44 | @PostMapping(value = "/api/v1/app/result", 45 | consumes = MediaType.APPLICATION_JSON_VALUE, 46 | produces = MediaType.APPLICATION_JSON_VALUE 47 | ) 48 | TestResult result(HashedGuid guid); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/client/TestResultServerClientConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.client; 22 | 23 | import app.coronawarn.verification.exception.VerificationServerException; 24 | import feign.Client; 25 | import feign.httpclient.ApacheHttpClient; 26 | import java.io.IOException; 27 | import java.security.GeneralSecurityException; 28 | import javax.net.ssl.HostnameVerifier; 29 | import javax.net.ssl.SSLContext; 30 | import lombok.RequiredArgsConstructor; 31 | import org.apache.http.conn.ssl.DefaultHostnameVerifier; 32 | import org.apache.http.conn.ssl.NoopHostnameVerifier; 33 | import org.apache.http.impl.client.HttpClientBuilder; 34 | import org.apache.http.ssl.SSLContextBuilder; 35 | import org.springframework.beans.factory.annotation.Value; 36 | import org.springframework.context.annotation.Bean; 37 | import org.springframework.context.annotation.Configuration; 38 | import org.springframework.http.HttpStatus; 39 | import org.springframework.util.ResourceUtils; 40 | 41 | @Configuration 42 | @RequiredArgsConstructor 43 | 44 | public class TestResultServerClientConfig { 45 | 46 | @Value("${cwa-testresult-server.ssl.enabled}") 47 | private boolean enabled; 48 | @Value("${cwa-testresult-server.ssl.one-way}") 49 | private boolean oneWay; 50 | @Value("${cwa-testresult-server.ssl.two-way}") 51 | private boolean twoWay; 52 | @Value("${cwa-testresult-server.ssl.hostname-verify}") 53 | private boolean hostnameVerify; 54 | @Value("${cwa-testresult-server.ssl.key-store}") 55 | private String keyStorePath; 56 | @Value("${cwa-testresult-server.ssl.key-store-password}") 57 | private char[] keyStorePassword; 58 | @Value("${cwa-testresult-server.ssl.trust-store}") 59 | private String trustStorePath; 60 | @Value("${cwa-testresult-server.ssl.trust-store-password}") 61 | private char[] trustStorePassword; 62 | 63 | /** 64 | * Configure the client dependent on the ssl properties. 65 | * 66 | * @return an Apache Http Client with or without SSL features 67 | */ 68 | @Bean 69 | public Client client() { 70 | if (enabled) { 71 | return new ApacheHttpClient( 72 | HttpClientBuilder 73 | .create() 74 | .setSSLContext(getSslContext()) 75 | .setSSLHostnameVerifier(getSslHostnameVerifier()) 76 | .build() 77 | ); 78 | } 79 | return new ApacheHttpClient(HttpClientBuilder.create() 80 | .setSSLHostnameVerifier(getSslHostnameVerifier()) 81 | .build()); 82 | } 83 | 84 | private SSLContext getSslContext() { 85 | try { 86 | SSLContextBuilder builder = SSLContextBuilder 87 | .create(); 88 | if (oneWay) { 89 | builder.loadTrustMaterial(ResourceUtils.getFile(trustStorePath), 90 | trustStorePassword); 91 | } 92 | if (twoWay) { 93 | builder.loadKeyMaterial(ResourceUtils.getFile(keyStorePath), 94 | keyStorePassword, 95 | keyStorePassword); 96 | } 97 | return builder.build(); 98 | } catch (IOException | GeneralSecurityException e) { 99 | throw new VerificationServerException(HttpStatus.INTERNAL_SERVER_ERROR, "The SSL context could not be loaded."); 100 | } 101 | } 102 | 103 | private HostnameVerifier getSslHostnameVerifier() { 104 | return hostnameVerify ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/config/LocalSecurityConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.config; 22 | 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 | import org.springframework.context.annotation.Bean; 25 | import org.springframework.context.annotation.Configuration; 26 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 27 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 28 | import org.springframework.security.web.SecurityFilterChain; 29 | 30 | @EnableWebSecurity 31 | @Configuration 32 | @ConditionalOnProperty(name = "server.ssl.client-auth", havingValue = "none", matchIfMissing = true) 33 | public class LocalSecurityConfig { 34 | 35 | /** 36 | * filter Chain. 37 | */ 38 | @Bean 39 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 40 | http 41 | .authorizeHttpRequests() 42 | .anyRequest().permitAll() 43 | .and().csrf().disable(); 44 | return http.build(); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/config/MtlsSecurityConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.config; 22 | 23 | import java.security.cert.CertificateEncodingException; 24 | import java.security.cert.X509Certificate; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | import java.util.stream.Stream; 28 | import lombok.RequiredArgsConstructor; 29 | import lombok.extern.slf4j.Slf4j; 30 | import org.apache.commons.codec.digest.DigestUtils; 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 32 | import org.springframework.context.annotation.Bean; 33 | import org.springframework.context.annotation.Configuration; 34 | import org.springframework.http.HttpMethod; 35 | import org.springframework.http.HttpStatus; 36 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 37 | import org.springframework.security.core.userdetails.User; 38 | import org.springframework.security.core.userdetails.UserDetailsService; 39 | import org.springframework.security.web.SecurityFilterChain; 40 | import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; 41 | import org.springframework.security.web.firewall.HttpFirewall; 42 | import org.springframework.security.web.firewall.StrictHttpFirewall; 43 | import org.springframework.web.server.ResponseStatusException; 44 | 45 | @Configuration 46 | @Slf4j 47 | @RequiredArgsConstructor 48 | @ConditionalOnProperty(name = "server.ssl.client-auth", havingValue = "need") 49 | public class MtlsSecurityConfig { 50 | 51 | private final VerificationApplicationConfig config; 52 | 53 | @Bean 54 | protected HttpFirewall strictFirewall() { 55 | StrictHttpFirewall firewall = new StrictHttpFirewall(); 56 | firewall.setAllowedHttpMethods(Arrays.asList( 57 | HttpMethod.GET.name(), 58 | HttpMethod.POST.name() 59 | )); 60 | return firewall; 61 | } 62 | 63 | /** 64 | * FilterChain. 65 | */ 66 | @Bean 67 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 68 | http 69 | .authorizeHttpRequests() 70 | .requestMatchers("/api/**").authenticated().and() 71 | .requiresChannel().requestMatchers("/api/**").requiresSecure().and() 72 | .x509().x509PrincipalExtractor(new ThumbprintX509PrincipalExtractor()).userDetailsService(userDetailsService()) 73 | .and().authorizeHttpRequests() 74 | .requestMatchers("/version/**").permitAll() 75 | .requestMatchers("/actuator/**").permitAll() 76 | .anyRequest().denyAll() 77 | .and().csrf().disable(); 78 | return http.build(); 79 | } 80 | 81 | private UserDetailsService userDetailsService() { 82 | return hash -> { 83 | 84 | boolean allowed = Stream.of(config.getAllowedClientCertificates() 85 | .split(",")) 86 | .map(String::trim) 87 | .anyMatch(entry -> entry.equalsIgnoreCase(hash)); 88 | 89 | if (allowed) { 90 | return new User(hash, "", Collections.emptyList()); 91 | } else { 92 | log.error("Failed to authenticate cert with hash {}", hash); 93 | throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); 94 | } 95 | }; 96 | } 97 | 98 | private static class ThumbprintX509PrincipalExtractor implements X509PrincipalExtractor { 99 | 100 | @Override 101 | public Object extractPrincipal(X509Certificate x509Certificate) { 102 | 103 | try { 104 | String hash = DigestUtils.sha256Hex(x509Certificate.getEncoded()); 105 | log.debug("Accessed by Subject {} Hash {}", x509Certificate.getSubjectX500Principal().getName(), hash); 106 | return hash; 107 | } catch (CertificateEncodingException e) { 108 | log.error("Failed to extract bytes from certificate"); 109 | return null; 110 | } 111 | } 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.config; 22 | 23 | import io.swagger.v3.oas.models.OpenAPI; 24 | import io.swagger.v3.oas.models.info.Info; 25 | import io.swagger.v3.oas.models.info.License; 26 | import lombok.RequiredArgsConstructor; 27 | import org.springframework.boot.info.BuildProperties; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.context.annotation.Configuration; 30 | 31 | /** 32 | * This class represents the open api config. 33 | */ 34 | @RequiredArgsConstructor 35 | @Configuration 36 | public class OpenApiConfig { 37 | 38 | private final BuildProperties buildProperties; 39 | 40 | /** 41 | * Configure the open api bean with build property values. 42 | * 43 | * @return the configured open api config 44 | */ 45 | @Bean 46 | public OpenAPI openApi() { 47 | return new OpenAPI() 48 | .info(new Info() 49 | .title(buildProperties.getArtifact()) 50 | .version(buildProperties.getVersion()) 51 | .license(new License() 52 | .name("Apache 2.0") 53 | .url("http://www.apache.org/licenses/LICENSE-2.0"))); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/config/RequestSizeLimitFilter.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.config; 22 | 23 | import jakarta.servlet.FilterChain; 24 | import jakarta.servlet.ServletException; 25 | import jakarta.servlet.http.HttpServletRequest; 26 | import jakarta.servlet.http.HttpServletResponse; 27 | import java.io.IOException; 28 | import lombok.RequiredArgsConstructor; 29 | import lombok.extern.slf4j.Slf4j; 30 | import org.apache.commons.lang3.StringUtils; 31 | import org.apache.http.HttpHeaders; 32 | import org.springframework.http.HttpStatus; 33 | import org.springframework.lang.NonNull; 34 | import org.springframework.stereotype.Component; 35 | import org.springframework.web.filter.OncePerRequestFilter; 36 | 37 | /** 38 | * A filter to avoid requests with a large content and chunked requests. 39 | */ 40 | @Component 41 | @Slf4j 42 | @RequiredArgsConstructor 43 | public class RequestSizeLimitFilter extends OncePerRequestFilter { 44 | 45 | private final VerificationApplicationConfig verificationApplicationConfig; 46 | 47 | @Override 48 | protected void doFilterInternal(HttpServletRequest request, 49 | @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) 50 | throws ServletException, IOException { 51 | long maxPostSize = verificationApplicationConfig.getRequest().getSizelimit(); 52 | if (request.getContentLengthLong() > maxPostSize || isChunkedRequest(request)) { 53 | log.warn("The request size is too large or the request was sent via chunks."); 54 | response.setStatus(HttpStatus.NOT_ACCEPTABLE.value()); 55 | return; 56 | } 57 | filterChain.doFilter(request, response); 58 | } 59 | 60 | private boolean isChunkedRequest(HttpServletRequest request) { 61 | String header = request.getHeader(HttpHeaders.TRANSFER_ENCODING); 62 | 63 | return !StringUtils.isEmpty(header) && header.equalsIgnoreCase("chunked"); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/config/VerificationApplicationConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.config; 22 | 23 | import lombok.Getter; 24 | import lombok.Setter; 25 | import org.springframework.boot.context.properties.ConfigurationProperties; 26 | 27 | /** 28 | * This class and its nested subclasses are used to read in values from configuration file application.yml, which is 29 | * loaded via the '@EnableConfigurationProperties' annotation from SpringBootApplication main class. 30 | */ 31 | @Getter 32 | @Setter 33 | @ConfigurationProperties 34 | public class VerificationApplicationConfig { 35 | private Long initialFakeDelayMilliseconds; 36 | 37 | private Long fakeDelayMovingAverageSamples; 38 | private String allowedClientCertificates; 39 | 40 | private Tan tan = new Tan(); 41 | private AppSession appsession = new AppSession(); 42 | private Entities entities = new Entities(); 43 | private Jwt jwt = new Jwt(); 44 | private Request request = new Request(); 45 | 46 | private boolean disableDobHashCheckForExternalTestResult; 47 | 48 | /** 49 | * Configure the Tan with build property values and return the configured parameters. 50 | */ 51 | @Getter 52 | @Setter 53 | public static class Tan { 54 | 55 | private Tele tele = new Tele(); 56 | private Valid valid = new Valid(); 57 | 58 | /** 59 | * Configure the Tele with build property values and return the configured parameters. 60 | */ 61 | @Getter 62 | @Setter 63 | public static class Tele { 64 | 65 | private Valid valid = new Valid(); 66 | private RateLimiting rateLimiting = new RateLimiting(); 67 | 68 | /** 69 | * Configure the TeleValid with build property values and return the configured parameters. 70 | */ 71 | @Getter 72 | @Setter 73 | public static class Valid { 74 | 75 | private String chars = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"; 76 | private int length = 1; 77 | // Number of hours that teleTAN remains valid 78 | private int hours = 1; 79 | private int eventDays = 2; 80 | } 81 | 82 | /** 83 | * Configure the rate limiting for creating new teletans. 84 | */ 85 | @Getter 86 | @Setter 87 | public static class RateLimiting { 88 | 89 | // Number of seconds for the rate limiting time window 90 | private int seconds = 3600; 91 | // Number of teletans that are allowed to create within time window 92 | private int count = 1000; 93 | // Threshold in percent for a warning in log stream 94 | private int thresholdInPercent = 80; 95 | } 96 | } 97 | 98 | /** 99 | * Configure the Valid with build property values and return the configured parameters. 100 | */ 101 | @Getter 102 | @Setter 103 | public static class Valid { 104 | 105 | // Number of days that TAN remains valid 106 | int days = 14; 107 | } 108 | } 109 | 110 | /** 111 | * Configure the AppSession with build property values and return the configured parameters. 112 | */ 113 | @Getter 114 | @Setter 115 | public static class AppSession { 116 | 117 | // Maximum number of tans in a session at one time 118 | int tancountermax = 1; 119 | } 120 | 121 | /** 122 | * Configure the Entities with build property values and return the configured parameters. 123 | */ 124 | @Getter 125 | @Setter 126 | public static class Entities { 127 | 128 | private Cleanup cleanup = new Cleanup(); 129 | 130 | /** 131 | * Configure the Cleanup with build property values and return the configured parameters. 132 | */ 133 | @Getter 134 | @Setter 135 | public static class Cleanup { 136 | 137 | private Integer days = 21; 138 | private String cron = "0 1 * * * *"; 139 | private Integer locklimit = 60; 140 | } 141 | 142 | } 143 | 144 | /** 145 | * Configure the Jwt with build property values and return the configured parameters. 146 | */ 147 | @Getter 148 | @Setter 149 | public static class Jwt { 150 | 151 | private String server = "http://localhost:8080"; 152 | private Boolean enabled = false; 153 | } 154 | 155 | /** 156 | * Configure the requests with build property values and return the configured parameters. 157 | */ 158 | @Getter 159 | @Setter 160 | public static class Request { 161 | 162 | private long sizelimit = 10000; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/controller/ExternalTanController.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.controller; 22 | 23 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 24 | 25 | import app.coronawarn.verification.config.VerificationApplicationConfig; 26 | import app.coronawarn.verification.domain.VerificationAppSession; 27 | import app.coronawarn.verification.exception.VerificationServerException; 28 | import app.coronawarn.verification.model.AppSessionSourceOfTrust; 29 | import app.coronawarn.verification.model.HashedGuid; 30 | import app.coronawarn.verification.model.LabTestResult; 31 | import app.coronawarn.verification.model.RegistrationToken; 32 | import app.coronawarn.verification.model.Tan; 33 | import app.coronawarn.verification.model.TanSourceOfTrust; 34 | import app.coronawarn.verification.model.TestResult; 35 | import app.coronawarn.verification.service.AppSessionService; 36 | import app.coronawarn.verification.service.FakeDelayService; 37 | import app.coronawarn.verification.service.FakeRequestService; 38 | import app.coronawarn.verification.service.TanService; 39 | import app.coronawarn.verification.service.TestResultServerService; 40 | import io.swagger.v3.oas.annotations.Operation; 41 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 42 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 43 | import jakarta.validation.Valid; 44 | import java.time.LocalDateTime; 45 | import java.util.Optional; 46 | import java.util.concurrent.Executors; 47 | import java.util.concurrent.ScheduledExecutorService; 48 | import lombok.NonNull; 49 | import lombok.RequiredArgsConstructor; 50 | import lombok.extern.slf4j.Slf4j; 51 | import org.apache.commons.lang3.RandomStringUtils; 52 | import org.springframework.context.annotation.Profile; 53 | import org.springframework.http.HttpStatus; 54 | import org.springframework.http.MediaType; 55 | import org.springframework.http.ResponseEntity; 56 | import org.springframework.util.StopWatch; 57 | import org.springframework.validation.annotation.Validated; 58 | import org.springframework.web.bind.annotation.PostMapping; 59 | import org.springframework.web.bind.annotation.RequestBody; 60 | import org.springframework.web.bind.annotation.RequestHeader; 61 | import org.springframework.web.bind.annotation.RequestMapping; 62 | import org.springframework.web.bind.annotation.RestController; 63 | import org.springframework.web.context.request.async.DeferredResult; 64 | 65 | /** 66 | * This class represents the rest controller for external tan interactions. 67 | */ 68 | @Slf4j 69 | @RequiredArgsConstructor 70 | @RestController 71 | @RequestMapping("/version/v1") 72 | @Validated 73 | @Profile("external") 74 | public class ExternalTanController { 75 | 76 | /** 77 | * The route to the tan generation endpoint. 78 | */ 79 | public static final String TAN_ROUTE = "/tan"; 80 | private static final Integer RESPONSE_PADDING_LENGTH = 15; 81 | private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4); 82 | 83 | @NonNull 84 | private final AppSessionService appSessionService; 85 | 86 | @NonNull 87 | private final FakeDelayService fakeDelayService; 88 | 89 | @NonNull 90 | private final VerificationApplicationConfig verificationApplicationConfig; 91 | 92 | @NonNull 93 | private final TestResultServerService testResultServerService; 94 | 95 | @NonNull 96 | private final TanService tanService; 97 | 98 | @NonNull 99 | private final FakeRequestService fakeRequestService; 100 | 101 | /** 102 | * This method generates a transaction number by a Registration Token, if the state of the COVID-19 lab-test is 103 | * positive. 104 | * 105 | * @param registrationToken generated by a hashed guid or a teleTAN. {@link RegistrationToken} 106 | * @param fake flag for fake request 107 | * @return A generated transaction number {@link Tan}. 108 | */ 109 | @Operation( 110 | summary = "Generates a Tan", 111 | description = "Generates a TAN on input of Registration Token. With the TAN one can submit his Diagnosis keys" 112 | ) 113 | @ApiResponses(value = { 114 | @ApiResponse(responseCode = "201", description = "Registration Token is valid"), 115 | @ApiResponse(responseCode = "400", description = "Registration Token does not exist")}) 116 | @PostMapping(value = TAN_ROUTE, 117 | consumes = MediaType.APPLICATION_JSON_VALUE, 118 | produces = MediaType.APPLICATION_JSON_VALUE 119 | ) 120 | public DeferredResult> generateTan(@Valid @RequestBody RegistrationToken registrationToken, 121 | @RequestHeader(value = "cwa-fake", required = false) 122 | String fake) { 123 | if ((fake != null) && (fake.equals("1"))) { 124 | return fakeRequestService.generateTan(registrationToken); 125 | } 126 | StopWatch stopWatch = new StopWatch(); 127 | stopWatch.start(); 128 | Optional actual 129 | = appSessionService.getAppSessionByToken(registrationToken.getRegistrationToken()); 130 | if (actual.isPresent()) { 131 | VerificationAppSession appSession = actual.get(); 132 | int tancountermax = verificationApplicationConfig.getAppsession().getTancountermax(); 133 | if (appSession.getTanCounter() < tancountermax) { 134 | AppSessionSourceOfTrust appSessionSourceOfTrust = appSession.getSourceOfTrust(); 135 | TanSourceOfTrust tanSourceOfTrust = TanSourceOfTrust.CONNECTED_LAB; 136 | switch (appSessionSourceOfTrust) { 137 | case HASHED_GUID -> { 138 | TestResult covidTestResult = testResultServerService.result(new HashedGuid(appSession.getHashedGuid())); 139 | if (covidTestResult.getTestResult() != LabTestResult.POSITIVE.getTestResult() 140 | && covidTestResult.getTestResult() != LabTestResult.QUICK_POSITIVE.getTestResult() 141 | ) { 142 | stopWatch.stop(); 143 | throw new VerificationServerException(HttpStatus.BAD_REQUEST, 144 | "Tan cannot be created, caused by the non positive result of the labserver"); 145 | } 146 | } 147 | case TELETAN -> tanSourceOfTrust = TanSourceOfTrust.TELETAN; 148 | default -> { 149 | stopWatch.stop(); 150 | throw new VerificationServerException(HttpStatus.BAD_REQUEST, 151 | "Unknown source of trust inside the appsession for the registration token"); 152 | } 153 | } 154 | appSession.incrementTanCounter(); 155 | appSession.setUpdatedAt(LocalDateTime.now()); 156 | 157 | appSessionService.saveAppSession(appSession); 158 | String generatedTan = tanService.generateVerificationTan(tanSourceOfTrust, appSession.getTeleTanType()); 159 | 160 | Tan returnTan = generateReturnTan(generatedTan, fake); 161 | stopWatch.stop(); 162 | fakeDelayService.updateFakeTanRequestDelay(stopWatch.getTotalTimeMillis()); 163 | DeferredResult> deferredResult = new DeferredResult<>(); 164 | scheduledExecutor.schedule(() -> deferredResult.setResult( 165 | ResponseEntity.status(HttpStatus.CREATED).body(returnTan)), 166 | fakeDelayService.realDelayTan(), MILLISECONDS); 167 | log.info("Returning the successfully generated tan."); 168 | return deferredResult; 169 | } 170 | throw new VerificationServerException(HttpStatus.BAD_REQUEST, 171 | "The maximum of generating tans for this registration token is reached"); 172 | } 173 | throw new VerificationServerException(HttpStatus.BAD_REQUEST, 174 | "VerificationAppSession not found for the registration token"); 175 | } 176 | 177 | private Tan generateReturnTan(String tan, String fake) { 178 | if (fake == null) { 179 | return new Tan(tan); 180 | } 181 | return new Tan(tan, RandomStringUtils.randomAlphanumeric(RESPONSE_PADDING_LENGTH)); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/controller/ExternalTokenController.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.controller; 22 | 23 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 24 | 25 | import app.coronawarn.verification.domain.VerificationTan; 26 | import app.coronawarn.verification.exception.VerificationServerException; 27 | import app.coronawarn.verification.model.RegistrationToken; 28 | import app.coronawarn.verification.model.RegistrationTokenKeyType; 29 | import app.coronawarn.verification.model.RegistrationTokenRequest; 30 | import app.coronawarn.verification.service.AppSessionService; 31 | import app.coronawarn.verification.service.FakeDelayService; 32 | import app.coronawarn.verification.service.FakeRequestService; 33 | import app.coronawarn.verification.service.TanService; 34 | import io.swagger.v3.oas.annotations.Operation; 35 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 36 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 37 | import jakarta.validation.Valid; 38 | import java.util.Optional; 39 | import java.util.concurrent.Executors; 40 | import java.util.concurrent.ScheduledExecutorService; 41 | import lombok.RequiredArgsConstructor; 42 | import lombok.extern.slf4j.Slf4j; 43 | import org.springframework.context.annotation.Profile; 44 | import org.springframework.http.HttpStatus; 45 | import org.springframework.http.MediaType; 46 | import org.springframework.http.ResponseEntity; 47 | import org.springframework.util.StopWatch; 48 | import org.springframework.validation.annotation.Validated; 49 | import org.springframework.web.bind.annotation.PostMapping; 50 | import org.springframework.web.bind.annotation.RequestBody; 51 | import org.springframework.web.bind.annotation.RequestHeader; 52 | import org.springframework.web.bind.annotation.RequestMapping; 53 | import org.springframework.web.bind.annotation.RestController; 54 | import org.springframework.web.context.request.async.DeferredResult; 55 | 56 | /** 57 | * This class represents the rest controller for externally reachable TAN interactions. 58 | */ 59 | @Slf4j 60 | @RequiredArgsConstructor 61 | @RestController 62 | @RequestMapping("/version/v1") 63 | @Validated 64 | @Profile("external") 65 | public class ExternalTokenController { 66 | 67 | /** 68 | * The route to the token registration endpoint. 69 | */ 70 | public static final String REGISTRATION_TOKEN_ROUTE = "/registrationToken"; 71 | 72 | private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4); 73 | 74 | private final FakeRequestService fakeRequestController; 75 | 76 | private final AppSessionService appSessionService; 77 | 78 | private final TanService tanService; 79 | 80 | private final FakeDelayService fakeDelayService; 81 | 82 | /** 83 | * This method generates a registrationToken by a hashed guid or a teleTAN. 84 | * 85 | * @param request {@link RegistrationTokenRequest} 86 | * @param fake flag for fake request 87 | * @return RegistrationToken - the created registration token {@link RegistrationToken} 88 | */ 89 | @Operation( 90 | summary = "Get registration Token", 91 | description = "Get a registration token by providing a SHA-256 hasehd GUID or a teleTAN") 92 | @ApiResponses(value = { 93 | @ApiResponse(responseCode = "201", description = "registration token generated."), 94 | @ApiResponse(responseCode = "400", description = "GUID/TeleTAN already exists.")}) 95 | @PostMapping(value = REGISTRATION_TOKEN_ROUTE, 96 | consumes = MediaType.APPLICATION_JSON_VALUE, 97 | produces = MediaType.APPLICATION_JSON_VALUE) 98 | public DeferredResult> generateRegistrationToken( 99 | @RequestBody @Valid RegistrationTokenRequest request, 100 | @RequestHeader(value = "cwa-fake", required = false) String fake) { 101 | if ((fake != null) && (fake.equals("1"))) { 102 | return fakeRequestController.generateRegistrationToken(request); 103 | } 104 | StopWatch stopWatch = new StopWatch(); 105 | stopWatch.start(); 106 | String key = request.getKey(); 107 | RegistrationTokenKeyType keyType = request.getKeyType(); 108 | DeferredResult> deferredResult = new DeferredResult<>(); 109 | 110 | switch (keyType) { 111 | case GUID -> { 112 | ResponseEntity responseEntity = 113 | appSessionService.generateRegistrationTokenByGuid(key, request.getKeyDob(), fake); 114 | stopWatch.stop(); 115 | fakeDelayService.updateFakeTokenRequestDelay(stopWatch.getTotalTimeMillis()); 116 | deferredResult.setResult(responseEntity); 117 | log.info("Returning the successfully generated RegistrationToken."); 118 | return deferredResult; 119 | } 120 | case TELETAN -> { 121 | Optional optional = tanService.getEntityByTan(key); 122 | ResponseEntity response = appSessionService.generateRegistrationTokenByTeleTan( 123 | key, 124 | fake, 125 | optional.map(VerificationTan::getTeleTanType).orElse(null)); 126 | if (optional.isPresent()) { 127 | VerificationTan teleTan = optional.get(); 128 | teleTan.setRedeemed(true); 129 | tanService.saveTan(teleTan); 130 | stopWatch.stop(); 131 | fakeDelayService.updateFakeTokenRequestDelay(stopWatch.getTotalTimeMillis()); 132 | scheduledExecutor.schedule(() -> deferredResult.setResult(response), fakeDelayService.realDelayToken(), 133 | MILLISECONDS); 134 | log.info("Returning the successfully generated RegistrationToken."); 135 | return deferredResult; 136 | } 137 | stopWatch.stop(); 138 | throw new VerificationServerException(HttpStatus.BAD_REQUEST, "The teleTAN verification failed"); 139 | } 140 | default -> { 141 | stopWatch.stop(); 142 | throw new VerificationServerException(HttpStatus.BAD_REQUEST, 143 | "Unknown registration key type for registration token"); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/controller/InternalTanController.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.controller; 22 | 23 | import app.coronawarn.verification.exception.VerificationServerException; 24 | import app.coronawarn.verification.model.AuthorizationRole; 25 | import app.coronawarn.verification.model.AuthorizationToken; 26 | import app.coronawarn.verification.model.Tan; 27 | import app.coronawarn.verification.model.TeleTan; 28 | import app.coronawarn.verification.model.TeleTanType; 29 | import app.coronawarn.verification.service.JwtService; 30 | import app.coronawarn.verification.service.TanService; 31 | import io.swagger.v3.oas.annotations.Operation; 32 | import io.swagger.v3.oas.annotations.headers.Header; 33 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 34 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 35 | import jakarta.validation.Valid; 36 | import java.time.LocalDateTime; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import lombok.NonNull; 40 | import lombok.RequiredArgsConstructor; 41 | import lombok.extern.slf4j.Slf4j; 42 | import org.springframework.context.annotation.Profile; 43 | import org.springframework.http.HttpStatus; 44 | import org.springframework.http.MediaType; 45 | import org.springframework.http.ResponseEntity; 46 | import org.springframework.validation.annotation.Validated; 47 | import org.springframework.web.bind.annotation.PostMapping; 48 | import org.springframework.web.bind.annotation.RequestBody; 49 | import org.springframework.web.bind.annotation.RequestHeader; 50 | import org.springframework.web.bind.annotation.RequestMapping; 51 | import org.springframework.web.bind.annotation.RestController; 52 | 53 | /** 54 | * This class represents the rest controller for internally needed tan operations. 55 | */ 56 | @Slf4j 57 | @RequiredArgsConstructor 58 | @RestController 59 | @RequestMapping("/version/v1") 60 | @Validated 61 | @Profile("internal") 62 | public class InternalTanController { 63 | 64 | /** 65 | * The route to the tan verification endpoint. 66 | */ 67 | public static final String TAN_VERIFY_ROUTE = "/tan/verify"; 68 | /** 69 | * The route to the teleTAN generation endpoint. 70 | */ 71 | public static final String TELE_TAN_ROUTE = "/tan/teletan"; 72 | 73 | public static final String TELE_TAN_TYPE_HEADER = "X-CWA-TELETAN-TYPE"; 74 | 75 | @NonNull 76 | private final TanService tanService; 77 | 78 | @NonNull 79 | private final JwtService jwtService; 80 | 81 | /** 82 | * This provided REST method verifies the transaction number (TAN). 83 | * 84 | * @param tan - the transaction number, which needs to be verified {@link Tan} 85 | * @return HTTP 200, if the verification was successful. Otherwise HTTP 404. 86 | */ 87 | @Operation( 88 | summary = "Verify provided Tan", 89 | description = "The provided Tan is verified to be formerly issued by the verification server" 90 | ) 91 | @ApiResponses(value = { 92 | @ApiResponse( 93 | responseCode = "200", 94 | description = "Tan is valid an formerly issued by the verification server", 95 | headers = { 96 | @Header(name = TELE_TAN_TYPE_HEADER, description = "Type of the TeleTan (TEST or EVENT)") 97 | }), 98 | @ApiResponse(responseCode = "404", description = "Tan could not be verified")}) 99 | @PostMapping(value = TAN_VERIFY_ROUTE, 100 | consumes = MediaType.APPLICATION_JSON_VALUE 101 | ) 102 | public ResponseEntity verifyTan(@Valid @RequestBody Tan tan) { 103 | return tanService.getEntityByTan(tan.getTan()) 104 | .filter(t -> t.canBeRedeemed(LocalDateTime.now())) 105 | .map(t -> { 106 | tanService.deleteTan(t); 107 | log.info("The Tan is valid."); 108 | return t; 109 | }) 110 | .map(t -> { 111 | ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok(); 112 | 113 | if (t.getTeleTanType() != null) { 114 | responseBuilder.header(TELE_TAN_TYPE_HEADER, t.getTeleTanType().toString()); 115 | } 116 | 117 | return responseBuilder.build(); 118 | }) 119 | .orElseGet(() -> { 120 | log.info("The Tan is invalid."); 121 | throw new VerificationServerException(HttpStatus.NOT_FOUND, "No Tan found or Tan is invalid"); 122 | }); 123 | } 124 | 125 | /** 126 | * This method generates a valid teleTAN. 127 | * 128 | * @param authorization auth 129 | * @return a created teletan 130 | */ 131 | @Operation( 132 | summary = "Request generation of a teleTAN", 133 | description = "A teleTAN is a human readable TAN with 7 characters which is supposed to be issued via call line" 134 | ) 135 | @ApiResponses(value = { 136 | @ApiResponse(responseCode = "201", description = "TeleTan created")}) 137 | @PostMapping(value = TELE_TAN_ROUTE, 138 | produces = MediaType.APPLICATION_JSON_VALUE 139 | ) 140 | public ResponseEntity createTeleTan( 141 | @RequestHeader(JwtService.HEADER_NAME_AUTHORIZATION) @Valid AuthorizationToken authorization, 142 | @RequestHeader(value = TELE_TAN_TYPE_HEADER, required = false) @Valid TeleTanType teleTanType) { 143 | 144 | List requiredRoles = new ArrayList<>(); 145 | 146 | if (teleTanType == null) { 147 | teleTanType = TeleTanType.TEST; 148 | requiredRoles.add(AuthorizationRole.AUTH_C19_HOTLINE); 149 | } else if (teleTanType == TeleTanType.EVENT) { 150 | requiredRoles.add(AuthorizationRole.AUTH_C19_HOTLINE_EVENT); 151 | } 152 | 153 | if (jwtService.isAuthorized(authorization.getToken(), requiredRoles)) { 154 | if (tanService.isTeleTanRateLimitNotExceeded()) { 155 | String teleTan = tanService.generateVerificationTeleTan(teleTanType); 156 | log.info("The teleTAN is generated."); 157 | return ResponseEntity.status(HttpStatus.CREATED).body(new TeleTan(teleTan)); 158 | } else { 159 | throw new VerificationServerException(HttpStatus.TOO_MANY_REQUESTS, "Rate Limit exceed. Try again later."); 160 | } 161 | } 162 | throw new VerificationServerException(HttpStatus.UNAUTHORIZED, "JWT is invalid."); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/controller/InternalTestStateController.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.controller; 22 | 23 | import app.coronawarn.verification.domain.VerificationAppSession; 24 | import app.coronawarn.verification.exception.VerificationServerException; 25 | import app.coronawarn.verification.model.AppSessionSourceOfTrust; 26 | import app.coronawarn.verification.model.HashedGuid; 27 | import app.coronawarn.verification.model.InternalTestResult; 28 | import app.coronawarn.verification.model.RegistrationToken; 29 | import app.coronawarn.verification.model.TestResult; 30 | import app.coronawarn.verification.service.AppSessionService; 31 | import app.coronawarn.verification.service.TestResultServerService; 32 | import io.swagger.v3.oas.annotations.Operation; 33 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 34 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 35 | import jakarta.validation.Valid; 36 | import java.util.Optional; 37 | import lombok.RequiredArgsConstructor; 38 | import lombok.extern.slf4j.Slf4j; 39 | import org.springframework.context.annotation.Profile; 40 | import org.springframework.http.HttpStatus; 41 | import org.springframework.http.MediaType; 42 | import org.springframework.http.ResponseEntity; 43 | import org.springframework.validation.annotation.Validated; 44 | import org.springframework.web.bind.annotation.PostMapping; 45 | import org.springframework.web.bind.annotation.RequestBody; 46 | import org.springframework.web.bind.annotation.RequestMapping; 47 | import org.springframework.web.bind.annotation.RestController; 48 | 49 | 50 | /** 51 | * This class represents the rest controller for requests regarding test states. 52 | */ 53 | @Slf4j 54 | @RequiredArgsConstructor 55 | @RestController 56 | @RequestMapping("/version/v1") 57 | @Validated 58 | @Profile("internal") 59 | public class InternalTestStateController { 60 | 61 | /** 62 | * The route to the test status of the COVID-19 test endpoint. 63 | */ 64 | public static final String TESTRESULT_ROUTE = "/testresult"; 65 | 66 | private final AppSessionService appSessionService; 67 | 68 | private final TestResultServerService testResultServerService; 69 | 70 | /** 71 | * Returns the test status of the COVID-19 test. 72 | * 73 | * @param registrationToken generated by a hashed guid {@link RegistrationToken} 74 | * @return result of the test, which can be POSITIVE, NEGATIVE, INVALID, PENDING, FAILED, 75 | * quick-test-POSITIVE, quick-test-NEGATIVE, quick-test-INVALID, quick-test-PENDING or quick-test-FAILED 76 | * will be POSITIVE for TeleTan 77 | */ 78 | @Operation( 79 | summary = "COVID-19 test result for given RegistrationToken", 80 | description = "Gets the result of COVID-19 Test. " 81 | + "If the RegistrationToken belongs to a TeleTan the result is always positive" 82 | ) 83 | @ApiResponses(value = { 84 | @ApiResponse(responseCode = "200", description = "Testresult retrieved"), 85 | @ApiResponse(responseCode = "403", description = "RegistrationToken is issued for TeleTan"), 86 | @ApiResponse(responseCode = "404", description = "RegistrationToken not found") 87 | }) 88 | @PostMapping(value = TESTRESULT_ROUTE, 89 | consumes = MediaType.APPLICATION_JSON_VALUE, 90 | produces = MediaType.APPLICATION_JSON_VALUE 91 | ) 92 | public ResponseEntity getTestState(@Valid @RequestBody RegistrationToken registrationToken) { 93 | 94 | Optional appSession = 95 | appSessionService.getAppSessionByToken(registrationToken.getRegistrationToken()); 96 | 97 | if (appSession.isPresent()) { 98 | AppSessionSourceOfTrust sourceOfTrust = appSession.get().getSourceOfTrust(); 99 | 100 | switch (sourceOfTrust) { 101 | case HASHED_GUID -> { 102 | HashedGuid hash = new HashedGuid(appSession.get().getHashedGuid()); 103 | TestResult testResult = testResultServerService.result(hash); 104 | 105 | // Check DOB Hash if present 106 | if (appSession.get().getHashedGuidDob() != null) { 107 | HashedGuid hashDob = new HashedGuid(appSession.get().getHashedGuidDob()); 108 | TestResult testResultDob = testResultServerService.result(hashDob); 109 | 110 | // TRS will always respond with a TestResult so we have to check if both results are equal 111 | if (testResultDob.getTestResult() != testResult.getTestResult()) { 112 | // given DOB Hash is invalid 113 | throw new VerificationServerException(HttpStatus.FORBIDDEN, 114 | "TestResult of dob hash does not equal to TestResult of hash"); 115 | } 116 | } 117 | log.debug("Result {}", testResult); 118 | log.info("The result for registration token based on hashed Guid will be returned."); 119 | return ResponseEntity.ok(new InternalTestResult( 120 | testResult.getTestResult(), 121 | testResult.getSc(), 122 | testResult.getLabId(), 123 | testResult.getResponsePadding(), 124 | appSession.get().getHashedGuid())); 125 | } 126 | case TELETAN -> { 127 | log.info("Internal TestState is not allowed for TeleTan Token."); 128 | throw new VerificationServerException(HttpStatus.FORBIDDEN, 129 | "Internal TestState is not allowed for TeleTan Token."); 130 | } 131 | default -> throw new VerificationServerException(HttpStatus.BAD_REQUEST, 132 | "Unknown source of trust inside the appsession for the registration token"); 133 | } 134 | } 135 | log.info("The registration token doesn't exist."); 136 | throw new VerificationServerException(HttpStatus.NOT_FOUND, 137 | "Registration Token not found"); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/controller/VerificationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.controller; 22 | 23 | import app.coronawarn.verification.exception.VerificationServerException; 24 | import jakarta.validation.ConstraintViolationException; 25 | import lombok.extern.slf4j.Slf4j; 26 | import org.springframework.http.HttpStatus; 27 | import org.springframework.http.ResponseEntity; 28 | import org.springframework.http.converter.HttpMessageNotReadableException; 29 | import org.springframework.web.HttpMediaTypeNotSupportedException; 30 | import org.springframework.web.HttpRequestMethodNotSupportedException; 31 | import org.springframework.web.bind.MethodArgumentNotValidException; 32 | import org.springframework.web.bind.ServletRequestBindingException; 33 | import org.springframework.web.bind.annotation.ExceptionHandler; 34 | import org.springframework.web.bind.annotation.ResponseStatus; 35 | import org.springframework.web.bind.annotation.RestControllerAdvice; 36 | import org.springframework.web.context.request.WebRequest; 37 | 38 | /** 39 | * This class represents the Exception Handler. 40 | */ 41 | @Slf4j 42 | @RestControllerAdvice 43 | public class VerificationExceptionHandler { 44 | 45 | /** 46 | * This method handles unknown Exceptions and Server Errors. 47 | * 48 | * @param ex the thrown exception 49 | * @param wr the WebRequest 50 | */ 51 | @ExceptionHandler(Exception.class) 52 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 53 | public void unknownException(Exception ex, WebRequest wr) { 54 | log.error("Unable to handle {}", wr.getDescription(false), ex); 55 | } 56 | 57 | /** 58 | * This method handles Bad Requests. 59 | * 60 | * @param ex the thrown exception 61 | * @param wr the WebRequest 62 | */ 63 | @ExceptionHandler({ 64 | HttpMessageNotReadableException.class, 65 | ServletRequestBindingException.class 66 | }) 67 | @ResponseStatus(HttpStatus.BAD_REQUEST) 68 | public void bindingExceptions(Exception ex, WebRequest wr) { 69 | log.error("Binding failed {}", wr.getDescription(false), ex); 70 | } 71 | 72 | /** 73 | * This method handles Validation Exceptions. 74 | * 75 | * @return ResponseEntity returns Bad Request 76 | */ 77 | @ExceptionHandler({ 78 | MethodArgumentNotValidException.class, 79 | ConstraintViolationException.class 80 | }) 81 | public ResponseEntity handleValidationExceptions() { 82 | return ResponseEntity.badRequest().build(); 83 | } 84 | 85 | /** 86 | * This method handles Validation Exceptions. 87 | * 88 | * @param exception the thrown exception 89 | * @return ResponseEntity returns a HTTP Status 90 | */ 91 | @ExceptionHandler(VerificationServerException.class) 92 | public ResponseEntity handleVerificationServerExceptions(VerificationServerException exception) { 93 | log.warn("The verification server response preventation due to: {}", exception.getMessage()); 94 | return ResponseEntity.status(exception.getHttpStatus()).build(); 95 | } 96 | 97 | /** 98 | * This method handles invalid HTTP methods. 99 | * 100 | * @param ex the thrown exception 101 | * @param wr the WebRequest 102 | */ 103 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) 104 | @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) 105 | public void methodNotAllowedException(Exception ex, WebRequest wr) { 106 | log.warn("Invalid http method {}", wr.getDescription(false), ex); 107 | } 108 | 109 | /** 110 | * This method handles unsupported content types. 111 | * 112 | * @param ex the thrown exception 113 | * @param wr the WebRequest 114 | */ 115 | @ExceptionHandler(HttpMediaTypeNotSupportedException.class) 116 | @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) 117 | public void contentTypeNotAllowedException(Exception ex, WebRequest wr) { 118 | log.warn("Unsupported content type {}", wr.getDescription(false), ex); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/domain/VerificationAppSession.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.domain; 22 | 23 | import app.coronawarn.verification.model.AppSessionSourceOfTrust; 24 | import app.coronawarn.verification.model.TeleTanType; 25 | import jakarta.persistence.Column; 26 | import jakarta.persistence.Entity; 27 | import jakarta.persistence.EnumType; 28 | import jakarta.persistence.Enumerated; 29 | import jakarta.persistence.GeneratedValue; 30 | import jakarta.persistence.GenerationType; 31 | import jakarta.persistence.Id; 32 | import jakarta.persistence.Table; 33 | import jakarta.persistence.Version; 34 | import java.io.Serializable; 35 | import java.time.LocalDateTime; 36 | import lombok.AllArgsConstructor; 37 | import lombok.EqualsAndHashCode; 38 | import lombok.Getter; 39 | import lombok.NoArgsConstructor; 40 | import lombok.Setter; 41 | 42 | /** 43 | * This class represents the AppSession-entity. 44 | */ 45 | @Getter 46 | @Setter 47 | @EqualsAndHashCode 48 | @NoArgsConstructor 49 | @AllArgsConstructor 50 | @Entity 51 | @Table(name = "app_session") 52 | public class VerificationAppSession implements Serializable { 53 | 54 | private static final long serialVersionUID = 1L; 55 | 56 | @Id 57 | @GeneratedValue(strategy = GenerationType.IDENTITY) 58 | @Column(name = "id") 59 | private Long id; 60 | 61 | @Version 62 | @Column(name = "version") 63 | private long version; 64 | 65 | @Column(name = "created_at") 66 | private LocalDateTime createdAt; 67 | 68 | @Column(name = "updated_at") 69 | private LocalDateTime updatedAt; 70 | 71 | @Column(name = "hashed_guid") 72 | private String hashedGuid; 73 | 74 | @Column(name = "hashed_guid_dob") 75 | private String hashedGuidDob; 76 | 77 | @Column(name = "registration_token_hash") 78 | private String registrationTokenHash; 79 | 80 | @Column(name = "tele_tan_hash") 81 | private String teleTanHash; 82 | 83 | @Column(name = "tan_counter") 84 | private int tanCounter; 85 | 86 | @Column(name = "sot") 87 | @Enumerated(EnumType.STRING) 88 | private AppSessionSourceOfTrust sourceOfTrust; 89 | 90 | @Column(name = "teletan_type") 91 | @Enumerated(EnumType.STRING) 92 | private TeleTanType teleTanType; 93 | 94 | /** 95 | * This method increments the tan counter. 96 | */ 97 | public void incrementTanCounter() { 98 | this.tanCounter++; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/domain/VerificationTan.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.domain; 22 | 23 | import app.coronawarn.verification.model.TanSourceOfTrust; 24 | import app.coronawarn.verification.model.TanType; 25 | import app.coronawarn.verification.model.TeleTanType; 26 | import jakarta.persistence.Column; 27 | import jakarta.persistence.Entity; 28 | import jakarta.persistence.EnumType; 29 | import jakarta.persistence.Enumerated; 30 | import jakarta.persistence.GeneratedValue; 31 | import jakarta.persistence.GenerationType; 32 | import jakarta.persistence.Id; 33 | import jakarta.persistence.Table; 34 | import jakarta.persistence.Version; 35 | import java.io.Serializable; 36 | import java.time.LocalDateTime; 37 | import lombok.AllArgsConstructor; 38 | import lombok.EqualsAndHashCode; 39 | import lombok.Getter; 40 | import lombok.NoArgsConstructor; 41 | import lombok.Setter; 42 | 43 | /** 44 | * This class represents the TAN - entity. 45 | */ 46 | @Getter 47 | @Setter 48 | @EqualsAndHashCode 49 | @NoArgsConstructor 50 | @AllArgsConstructor 51 | @Entity 52 | @Table(name = "tan") 53 | public class VerificationTan implements Serializable { 54 | 55 | static final long SERIAL_VERSION_UID = 1L; 56 | 57 | @Id 58 | @GeneratedValue(strategy = GenerationType.IDENTITY) 59 | @Column(name = "id") 60 | private Long id; 61 | 62 | @Version 63 | @Column(name = "version") 64 | private long version; 65 | 66 | @Column(name = "created_at") 67 | private LocalDateTime createdAt; 68 | 69 | @Column(name = "updated_at") 70 | private LocalDateTime updatedAt; 71 | 72 | @Column(name = "tan_hash") 73 | private String tanHash; 74 | 75 | @Column(name = "valid_from") 76 | private LocalDateTime validFrom; 77 | 78 | @Column(name = "valid_until") 79 | private LocalDateTime validUntil; 80 | 81 | @Column(name = "sot") 82 | @Enumerated(EnumType.STRING) 83 | private TanSourceOfTrust sourceOfTrust; 84 | 85 | @Column(name = "redeemed") 86 | private boolean redeemed; 87 | 88 | @Column(name = "type") 89 | @Enumerated(EnumType.STRING) 90 | private TanType type; 91 | 92 | @Column(name = "teletan_type") 93 | @Enumerated(EnumType.STRING) 94 | private TeleTanType teleTanType; 95 | 96 | /** 97 | * Check if the tan can be redeemed by date. 98 | * 99 | * @param reference the date to check if it is in between from and until range 100 | * @return true or false if it can be redeemed 101 | */ 102 | public boolean canBeRedeemed(LocalDateTime reference) { 103 | return validFrom.isBefore(reference) 104 | && validUntil.isAfter(reference) 105 | && !isRedeemed(); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/exception/VerificationServerException.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.exception; 22 | 23 | import lombok.Getter; 24 | import org.springframework.http.HttpStatus; 25 | 26 | /** 27 | * This class represents the Verification Server Exception. 28 | */ 29 | @Getter 30 | public class VerificationServerException extends RuntimeException { 31 | 32 | private final HttpStatus httpStatus; 33 | 34 | /** 35 | * The Constructor for the Exception class. 36 | * 37 | * @param httpStatus the state of the server 38 | * @param message the message 39 | */ 40 | public VerificationServerException(HttpStatus httpStatus, String message) { 41 | super(message); 42 | this.httpStatus = httpStatus; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/AppSessionSourceOfTrust.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | /** 24 | * This class represents the possible sources of trust for an appsession entity. 25 | * 26 | * @see Entity AppSession - sourceOfTrust 27 | */ 28 | public enum AppSessionSourceOfTrust { 29 | HASHED_GUID, 30 | TELETAN 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/AuthorizationRole.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import lombok.Getter; 24 | 25 | /** 26 | * The different possible roles, which are authorizated to create a tele tan. 27 | * 28 | * @see 29 | * 30 | * Use Case - Create TeleTan 31 | */ 32 | @Getter 33 | public enum AuthorizationRole { 34 | AUTH_C19_HOTLINE("c19hotline"), 35 | AUTH_C19_HEALTHAUTHORITY("c19healthauthority"), 36 | AUTH_C19_HOTLINE_EVENT("c19hotline_event"); 37 | 38 | private final String roleName; 39 | 40 | AuthorizationRole(final String role) { 41 | this.roleName = role; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/AuthorizationToken.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import jakarta.validation.constraints.NotNull; 25 | import lombok.AllArgsConstructor; 26 | import lombok.Data; 27 | import lombok.NoArgsConstructor; 28 | 29 | /** 30 | * This class represents the authorization bearer token (JWT), which is used for the 31 | * creation of a teleTan. 32 | */ 33 | @Schema( 34 | description = "The bearer jwt token header model." 35 | ) 36 | @Data 37 | @NoArgsConstructor 38 | @AllArgsConstructor 39 | public class AuthorizationToken { 40 | 41 | @NotNull 42 | private String token; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/Certs.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | import lombok.AllArgsConstructor; 27 | import lombok.Data; 28 | import lombok.NoArgsConstructor; 29 | 30 | @Data 31 | @NoArgsConstructor 32 | @AllArgsConstructor 33 | public class Certs { 34 | 35 | private List keys = null; 36 | private Map additionalProperties = new HashMap<>(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/HashedGuid.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.AllArgsConstructor; 25 | import lombok.Data; 26 | import lombok.NoArgsConstructor; 27 | 28 | /** 29 | * This class represents the hashed Guid. 30 | * Hash (SHA256) aka QR-Code, GUID encoded as hex string. 31 | * 32 | * @see Core Entities 33 | */ 34 | @Schema( 35 | description = "The hashed Guid request model." 36 | ) 37 | @Data 38 | @NoArgsConstructor 39 | @AllArgsConstructor 40 | public class HashedGuid { 41 | private String id; 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/InternalTestResult.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.AllArgsConstructor; 25 | import lombok.Getter; 26 | import lombok.NoArgsConstructor; 27 | 28 | 29 | /** 30 | * This class represents the TestResult. 31 | * 32 | * @see Core Entities 33 | */ 34 | @Schema( 35 | description = "The test result model for internal test result requests." 36 | ) 37 | @NoArgsConstructor 38 | @AllArgsConstructor 39 | @Getter 40 | public class InternalTestResult extends TestResult { 41 | 42 | private String testId; 43 | 44 | public InternalTestResult(int testResult, long sc, String labId, String responsePadding, String testId) { 45 | super(testResult, sc, labId, responsePadding); 46 | this.testId = testId; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/Key.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | import lombok.AllArgsConstructor; 27 | import lombok.Data; 28 | import lombok.NoArgsConstructor; 29 | 30 | @Data 31 | @NoArgsConstructor 32 | @AllArgsConstructor 33 | public class Key { 34 | public static final String SIG = "sig"; 35 | public static final String RS256 = "RS256"; 36 | private String kid; 37 | private String kty; 38 | private String alg; 39 | private String use; 40 | private String nn; 41 | private String ee; 42 | private List x5c = null; 43 | private String x5t; 44 | private String x5tS256; 45 | private final Map additionalProperties = new HashMap<>(); 46 | 47 | /** 48 | * Check if the cert is valid for use. 49 | * @return true, if the cert has the right use and alg keys, otherwise false 50 | */ 51 | 52 | public boolean isCertValid() { 53 | return getUse().equals(SIG) && getAlg().equals(RS256); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/LabTestResult.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.Getter; 25 | 26 | /** 27 | * The possible result results of the COVID-19 lab-test. 28 | * Pending = 0 : The test result does not exist yet 29 | * Negative = 1 : No indication for COVID-19 30 | * Positive = 2 : The test result indicates infection with COVID-19 31 | * Invalid = 3 : The test result is invalid due to unknown reason 32 | * Redeemed = 4 : The test result is redeemed by time 33 | */ 34 | 35 | @Schema( 36 | description = "The lab test result model." 37 | ) 38 | @Getter 39 | public enum LabTestResult { 40 | PENDING(0), 41 | NEGATIVE(1), 42 | POSITIVE(2), 43 | INVALID(3), 44 | REDEEMED(4), 45 | QUICK_PENDING(5), 46 | QUICK_NEGATIVE(6), 47 | QUICK_POSITIVE(7), 48 | QUICK_INVALID(8), 49 | QUICK_REDEEMED(9); 50 | 51 | /** 52 | * The identifier for the test result from the lab-server. 53 | */ 54 | private final int testResult; 55 | 56 | /** 57 | * The constructor. 58 | * 59 | * @param stateValue the lab test state 60 | */ 61 | LabTestResult(final int stateValue) { 62 | this.testResult = stateValue; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/RegistrationToken.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import com.fasterxml.jackson.annotation.JsonInclude; 24 | import io.swagger.v3.oas.annotations.media.Schema; 25 | import jakarta.persistence.Transient; 26 | import jakarta.validation.constraints.NotNull; 27 | import jakarta.validation.constraints.Pattern; 28 | import lombok.AllArgsConstructor; 29 | import lombok.Data; 30 | import lombok.NoArgsConstructor; 31 | import lombok.NonNull; 32 | import lombok.RequiredArgsConstructor; 33 | 34 | /** 35 | * This class represents the registration Token. 36 | */ 37 | @Schema( 38 | description = "The registration token model." 39 | ) 40 | @Data 41 | @NoArgsConstructor 42 | @RequiredArgsConstructor 43 | @AllArgsConstructor 44 | public class RegistrationToken { 45 | 46 | @NonNull 47 | @NotNull 48 | @Pattern(regexp = "^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$") 49 | private String registrationToken; 50 | 51 | @JsonInclude(JsonInclude.Include.NON_NULL) 52 | @Transient 53 | private String responsePadding; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/RegistrationTokenKeyType.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | /** 24 | * This class represent the different key types, with whom you can generate a registration token. 25 | */ 26 | public enum RegistrationTokenKeyType { 27 | 28 | /** 29 | * The key type GUID(Hash). 30 | */ 31 | GUID, 32 | /** 33 | * The key type TeleTaN, which can be generated by the hotline or a health 34 | * authority. 35 | */ 36 | TELETAN 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/RegistrationTokenRequest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import app.coronawarn.verification.validator.RegistrationTokenKeyConstraint; 24 | import io.swagger.v3.oas.annotations.media.Schema; 25 | import jakarta.validation.constraints.NotNull; 26 | import jakarta.validation.constraints.Pattern; 27 | import lombok.AllArgsConstructor; 28 | import lombok.Data; 29 | import lombok.NoArgsConstructor; 30 | 31 | /** 32 | * This class represents a registration token request parameter with a hashed guid or a teleTAN. 33 | */ 34 | @Schema( 35 | description = "The registration token request model." 36 | ) 37 | @Data 38 | @NoArgsConstructor 39 | @AllArgsConstructor 40 | @RegistrationTokenKeyConstraint 41 | public class RegistrationTokenRequest { 42 | 43 | /** 44 | * The key which can be a teletan or a hashed guid. 45 | */ 46 | @NotNull 47 | private String key; 48 | 49 | /** 50 | * The hashed GUID built with date of birth. 51 | */ 52 | @Pattern(regexp = "^[XxA-Fa-f0-9]([A-Fa-f0-9]{63})$") 53 | private String keyDob; 54 | 55 | /** 56 | * The type of key, which can be "GUID" or "TELETAN". 57 | */ 58 | @NotNull 59 | private RegistrationTokenKeyType keyType; 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/Tan.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import com.fasterxml.jackson.annotation.JsonInclude; 24 | import io.swagger.v3.oas.annotations.media.Schema; 25 | import jakarta.persistence.Transient; 26 | import jakarta.validation.constraints.NotNull; 27 | import jakarta.validation.constraints.Pattern; 28 | import lombok.AllArgsConstructor; 29 | import lombok.Data; 30 | import lombok.NoArgsConstructor; 31 | import lombok.NonNull; 32 | import lombok.RequiredArgsConstructor; 33 | 34 | /** 35 | * This class represents the transaction number. 36 | */ 37 | @Schema( 38 | description = "The transaction number model." 39 | ) 40 | @Data 41 | @AllArgsConstructor 42 | @RequiredArgsConstructor 43 | @NoArgsConstructor 44 | public class Tan { 45 | 46 | @NotNull 47 | @NonNull 48 | @Pattern(regexp = "^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$") 49 | private String tan; 50 | 51 | @JsonInclude(JsonInclude.Include.NON_NULL) 52 | @Transient 53 | private String responsePadding; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/TanSourceOfTrust.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | /** 24 | * This class represents the possible sources of trust for a TAN entity. 25 | * 26 | * @see Entity TAN - sourceOfTrust 27 | */ 28 | public enum TanSourceOfTrust { 29 | CONNECTED_LAB, 30 | TELETAN 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/TanType.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | 25 | /** 26 | * This class represents the different types of tans. 27 | * 28 | * @see Entity TAN - Type 29 | */ 30 | @Schema 31 | public enum TanType { 32 | /** 33 | * The general tan. 34 | */ 35 | TAN, 36 | 37 | /** 38 | * The TeleTAN, which was created by, e.g the hotline. 39 | */ 40 | TELETAN 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/TeleTan.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | import lombok.AllArgsConstructor; 25 | import lombok.Data; 26 | import lombok.NoArgsConstructor; 27 | 28 | /** 29 | * This class represents the tele transaction number (teleTAN). 30 | */ 31 | @Schema( 32 | description = "The teleTAN model." 33 | ) 34 | @Data 35 | @NoArgsConstructor 36 | @AllArgsConstructor 37 | public class TeleTan { 38 | private String value; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/TeleTanType.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import io.swagger.v3.oas.annotations.media.Schema; 24 | 25 | @Schema( 26 | description = "The TeleTan Type model." 27 | ) 28 | public enum TeleTanType { 29 | 30 | @Schema(description = "TeleTan is issued because of a positive PCR Test") 31 | TEST, 32 | 33 | @Schema(description = "TeleTan is issued because of a confirmed infection at an event.") 34 | EVENT 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/model/TestResult.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.model; 22 | 23 | import com.fasterxml.jackson.annotation.JsonInclude; 24 | import io.swagger.v3.oas.annotations.media.Schema; 25 | import jakarta.persistence.Transient; 26 | import lombok.AllArgsConstructor; 27 | import lombok.Data; 28 | import lombok.NoArgsConstructor; 29 | 30 | 31 | /** 32 | * This class represents the TestResult. 33 | * 34 | * @see Core Entities 35 | */ 36 | @Schema( 37 | description = "The test result model." 38 | ) 39 | @Data 40 | @NoArgsConstructor 41 | @AllArgsConstructor 42 | public class TestResult { 43 | 44 | private int testResult; 45 | 46 | private long sc; 47 | 48 | @JsonInclude(JsonInclude.Include.NON_NULL) 49 | private String labId; 50 | 51 | @JsonInclude(JsonInclude.Include.NON_NULL) 52 | @Transient 53 | private String responsePadding; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/repository/VerificationAppSessionRepository.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.repository; 22 | 23 | import app.coronawarn.verification.domain.VerificationAppSession; 24 | import java.time.LocalDateTime; 25 | import java.util.Optional; 26 | import org.springframework.data.jpa.repository.JpaRepository; 27 | import org.springframework.data.jpa.repository.Modifying; 28 | import org.springframework.data.jpa.repository.Query; 29 | 30 | /** 31 | * This class represents the AppSession repository. 32 | */ 33 | public interface VerificationAppSessionRepository extends JpaRepository { 34 | 35 | /** 36 | * This method looks in the Database for an Appsession with the given registrationTokenHash. 37 | * 38 | * @param registrationTokenHash hash to search for 39 | * @return Optional VerificationAppSession the optional Appsession 40 | */ 41 | Optional findByRegistrationTokenHash(String registrationTokenHash); 42 | 43 | /** 44 | * This method looks in the Database for an Appsession with the given hashedGuid. 45 | * 46 | * @param hashedGuid hash to search for 47 | * @param hashedGuidDob hash to search for 48 | * @return Optional VerificationAppSession the optional Appsession 49 | */ 50 | Optional findByHashedGuidOrHashedGuidDob(String hashedGuid, String hashedGuidDob); 51 | 52 | /** 53 | * This method looks in the Database for an Appsession with the given teleTanHash. 54 | * 55 | * @param teleTanHash hash to search for 56 | * @return Optional VerificationAppSession the optional Appsession 57 | */ 58 | Optional findByTeleTanHash(String teleTanHash); 59 | 60 | /** 61 | * This method looks in the Database for Appsessions that are older than the before value and deletes them. 62 | * 63 | * @param before the Date to delete by 64 | */ 65 | @Modifying 66 | @Query("delete from VerificationAppSession a where a.createdAt < ?1") 67 | void deleteByCreatedAtBefore(LocalDateTime before); 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/repository/VerificationTanRepository.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.repository; 22 | 23 | import app.coronawarn.verification.domain.VerificationTan; 24 | import app.coronawarn.verification.model.TanType; 25 | import java.time.LocalDateTime; 26 | import java.util.Optional; 27 | import org.springframework.data.jpa.repository.JpaRepository; 28 | import org.springframework.data.jpa.repository.Modifying; 29 | import org.springframework.data.jpa.repository.Query; 30 | 31 | /** 32 | * This class represents the Tan repository. 33 | */ 34 | public interface VerificationTanRepository extends JpaRepository { 35 | 36 | /** 37 | * This method looks in the Database for an if a VerificationTan exists for the tan hash. 38 | * 39 | * @param tanHash hash to search for 40 | * @return Boolean if there is an Entity for the tanHash 41 | */ 42 | boolean existsByTanHash(String tanHash); 43 | 44 | /** 45 | * This method looks in the Database for an if a VerificationTan exists for the tan hash. 46 | * 47 | * @param tanHash hash to search for 48 | * @return Optional VerificationTan 49 | */ 50 | Optional findByTanHash(String tanHash); 51 | 52 | /** 53 | * This method purges Entities from the database that are older than before value. 54 | * 55 | * @param before LocalDateTime to delete older entities 56 | */ 57 | @Modifying 58 | @Query("delete from VerificationTan a where a.createdAt < ?1") 59 | void deleteByCreatedAtBefore(LocalDateTime before); 60 | 61 | /** 62 | * This method counts entities which are newer then after value. 63 | * 64 | * @param after - LocalDateTime to count entities 65 | * @param tanType - TanType of the tans that should be counted 66 | * @return number of relevant entities 67 | */ 68 | int countByCreatedAtIsAfterAndTypeIs(LocalDateTime after, TanType tanType); 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/AppSessionService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import app.coronawarn.verification.domain.VerificationAppSession; 24 | import app.coronawarn.verification.exception.VerificationServerException; 25 | import app.coronawarn.verification.model.AppSessionSourceOfTrust; 26 | import app.coronawarn.verification.model.RegistrationToken; 27 | import app.coronawarn.verification.model.TeleTanType; 28 | import app.coronawarn.verification.repository.VerificationAppSessionRepository; 29 | import java.time.LocalDateTime; 30 | import java.util.Optional; 31 | import java.util.UUID; 32 | import lombok.NonNull; 33 | import lombok.RequiredArgsConstructor; 34 | import lombok.extern.slf4j.Slf4j; 35 | import org.apache.commons.lang3.RandomStringUtils; 36 | import org.springframework.dao.DataIntegrityViolationException; 37 | import org.springframework.http.HttpStatus; 38 | import org.springframework.http.ResponseEntity; 39 | import org.springframework.stereotype.Component; 40 | 41 | /** 42 | * This class represents the VerificationAppSession service. 43 | */ 44 | @Slf4j 45 | @RequiredArgsConstructor 46 | @Component 47 | public class AppSessionService { 48 | 49 | private static final Integer TOKEN_PADDING_LENGTH = 1; 50 | /** 51 | * The {@link VerificationAppSessionRepository}. 52 | */ 53 | @NonNull 54 | private final VerificationAppSessionRepository appSessionRepository; 55 | 56 | /** 57 | * The {@link HashingService}. 58 | */ 59 | @NonNull 60 | private final HashingService hashingService; 61 | 62 | /** 63 | * Creates an AppSession-Entity. 64 | * 65 | * @param registrationToken Token for registration 66 | * @return appSession for registrationToken 67 | */ 68 | public VerificationAppSession generateAppSession(String registrationToken) { 69 | log.info("Create the app session entity with the created registration token."); 70 | VerificationAppSession appSession = new VerificationAppSession(); 71 | appSession.setCreatedAt(LocalDateTime.now()); 72 | appSession.setUpdatedAt(LocalDateTime.now()); 73 | appSession.setRegistrationTokenHash(hashingService.hash(registrationToken)); 74 | return appSession; 75 | } 76 | 77 | private String generateRegistrationToken() { 78 | return UUID.randomUUID().toString(); 79 | } 80 | 81 | /** 82 | * This method generates a registration Token by a guid . 83 | * 84 | * @param hashedGuid the hashed guid 85 | * @return an {@link ResponseEntity} 86 | */ 87 | public ResponseEntity generateRegistrationTokenByGuid( 88 | String hashedGuid, String hashedGuidDob, String fake) { 89 | 90 | if (checkRegistrationTokenAlreadyExistsForGuid(hashedGuid)) { 91 | log.warn("The registration token already exists for the hashed guid."); 92 | return ResponseEntity.badRequest().build(); 93 | } 94 | 95 | if (hashedGuidDob != null && checkRegistrationTokenAlreadyExistsForGuid(hashedGuidDob)) { 96 | log.warn("The registration token already exists for the hashed guid dob."); 97 | return ResponseEntity.badRequest().build(); 98 | } 99 | 100 | log.info("Start generating a new registration token for the given hashed guid."); 101 | 102 | String registrationToken = generateRegistrationToken(); 103 | VerificationAppSession appSession = generateAppSession(registrationToken); 104 | appSession.setHashedGuid(hashedGuid); 105 | appSession.setHashedGuidDob(hashedGuidDob); 106 | appSession.setSourceOfTrust(AppSessionSourceOfTrust.HASHED_GUID); 107 | 108 | try { 109 | saveAppSession(appSession); 110 | } catch (DataIntegrityViolationException e) { 111 | log.error("Failed to save RegistrationToken because of Hashed GUID Conflict: {}", hashedGuid); 112 | throw new VerificationServerException( 113 | HttpStatus.BAD_REQUEST, "Failed to save RegistrationToken because of Hashed GUID Conflict"); 114 | } 115 | 116 | log.info("Returning the successfully created registration token."); 117 | return ResponseEntity.status(HttpStatus.CREATED).body( 118 | getBackwardCompatibleRegistrationToken(registrationToken, fake)); 119 | 120 | } 121 | 122 | /** 123 | * This method generates a registration Token by a TeleTAN. 124 | * 125 | * @param teleTan the TeleTan 126 | * @return an {@link ResponseEntity} 127 | */ 128 | public ResponseEntity generateRegistrationTokenByTeleTan( 129 | String teleTan, String fake, TeleTanType teleTanType) { 130 | if (checkRegistrationTokenAlreadyExistForTeleTan(teleTan)) { 131 | log.warn("The registration token already exists for this TeleTAN."); 132 | return ResponseEntity.badRequest().build(); 133 | } else { 134 | log.info("Start generating a new registration token for the given TeleTAN."); 135 | String registrationToken = generateRegistrationToken(); 136 | VerificationAppSession appSession = generateAppSession(registrationToken); 137 | appSession.setTeleTanHash(hashingService.hash(teleTan)); 138 | appSession.setSourceOfTrust(AppSessionSourceOfTrust.TELETAN); 139 | appSession.setTeleTanType(teleTanType); 140 | saveAppSession(appSession); 141 | log.info("Returning the successfully created registration token."); 142 | return ResponseEntity.status(HttpStatus.CREATED).body( 143 | getBackwardCompatibleRegistrationToken(registrationToken, fake)); 144 | } 145 | } 146 | 147 | /** 148 | * Persists the specified entity of {@link VerificationAppSession} instances. 149 | * 150 | * @param appSession the verification app session entity 151 | */ 152 | public void saveAppSession(VerificationAppSession appSession) { 153 | log.info("Start saveAppSession."); 154 | appSessionRepository.save(appSession); 155 | } 156 | 157 | /** 158 | * Get existing VerificationAppSession for Reg Token from {@link VerificationAppSessionRepository}. 159 | * 160 | * @param registrationToken the registrationToken 161 | * @return Optional VerificationAppSession 162 | */ 163 | public Optional getAppSessionByToken(String registrationToken) { 164 | log.info("Start getAppSessionByToken."); 165 | return appSessionRepository.findByRegistrationTokenHash(hashingService.hash(registrationToken)); 166 | } 167 | 168 | /** 169 | * Check for existing hashed GUID Token in the {@link VerificationAppSessionRepository}. 170 | * 171 | * @param hashedGuid the hashed guid 172 | * @return flag for existing guid 173 | */ 174 | public boolean checkRegistrationTokenAlreadyExistsForGuid(String hashedGuid) { 175 | log.info("Start checkRegistrationTokenAlreadyExistsForGuid."); 176 | return appSessionRepository.findByHashedGuidOrHashedGuidDob(hashedGuid, hashedGuid).isPresent(); 177 | } 178 | 179 | /** 180 | * Check for existing hashed TeleTAN in the {@link VerificationAppSessionRepository}. 181 | * 182 | * @param teleTan the teleTAN 183 | * @return flag for existing teleTAN 184 | */ 185 | public boolean checkRegistrationTokenAlreadyExistForTeleTan(String teleTan) { 186 | log.info("Start checkTeleTanAlreadyExistForTeleTan."); 187 | return appSessionRepository.findByTeleTanHash(hashingService.hash(teleTan)).isPresent(); 188 | } 189 | 190 | private RegistrationToken getBackwardCompatibleRegistrationToken(String registrationToken, String fake) { 191 | if (fake == null) { 192 | return new RegistrationToken(registrationToken); 193 | } 194 | return new RegistrationToken(registrationToken, RandomStringUtils.randomAlphanumeric(TOKEN_PADDING_LENGTH)); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/EntitiesCleanupService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import app.coronawarn.verification.config.VerificationApplicationConfig; 24 | import app.coronawarn.verification.repository.VerificationAppSessionRepository; 25 | import app.coronawarn.verification.repository.VerificationTanRepository; 26 | import jakarta.transaction.Transactional; 27 | import java.time.LocalDateTime; 28 | import java.time.Period; 29 | import lombok.RequiredArgsConstructor; 30 | import lombok.extern.slf4j.Slf4j; 31 | import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; 32 | import org.springframework.scheduling.annotation.Scheduled; 33 | import org.springframework.stereotype.Component; 34 | 35 | /** 36 | * A Service to delete entities that are older than configured days. 37 | */ 38 | @Slf4j 39 | @RequiredArgsConstructor 40 | @Component 41 | public class EntitiesCleanupService { 42 | 43 | private final VerificationApplicationConfig applicationConfig; 44 | private final VerificationAppSessionRepository appSessionRepository; 45 | private final VerificationTanRepository tanRepository; 46 | 47 | /** 48 | * All entities that are older than configured days get deleted. 49 | */ 50 | @Scheduled( 51 | cron = "${entities.cleanup.cron}" 52 | ) 53 | @SchedulerLock(name = "VerificationCleanupService_cleanup", lockAtLeastFor = "PT0S", 54 | lockAtMostFor = "${entities.cleanup.locklimit}") 55 | @Transactional 56 | public void cleanup() { 57 | log.info("cleanup execution"); 58 | appSessionRepository.deleteByCreatedAtBefore(LocalDateTime.now() 59 | .minus(Period.ofDays(applicationConfig.getEntities().getCleanup().getDays()))); 60 | tanRepository.deleteByCreatedAtBefore(LocalDateTime.now() 61 | .minus(Period.ofDays(applicationConfig.getEntities().getCleanup().getDays()))); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/FakeDelayService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import app.coronawarn.verification.config.VerificationApplicationConfig; 24 | import org.apache.commons.math3.distribution.PoissonDistribution; 25 | import org.springframework.stereotype.Component; 26 | 27 | /** 28 | * {@link FakeDelayService} instances manage the response delay in the processing of fake (or "dummy") requests. 29 | */ 30 | @Component 31 | public class FakeDelayService { 32 | 33 | private final long movingAverageSampleSize; 34 | private long fakeDelayTest; 35 | 36 | private long fakeDelayTan; 37 | 38 | private long fakeDelayToken; 39 | 40 | /** 41 | * Constructor for the FakeDelayService. 42 | */ 43 | public FakeDelayService(VerificationApplicationConfig applicationConfig) { 44 | this.fakeDelayTest = applicationConfig.getInitialFakeDelayMilliseconds(); 45 | this.fakeDelayTan = applicationConfig.getInitialFakeDelayMilliseconds(); 46 | this.fakeDelayToken = applicationConfig.getInitialFakeDelayMilliseconds(); 47 | this.movingAverageSampleSize = applicationConfig.getFakeDelayMovingAverageSamples(); 48 | } 49 | 50 | /** 51 | * Returns the current fake delay after applying random jitter. 52 | */ 53 | public long getJitteredFakeTanDelay() { 54 | return new PoissonDistribution(fakeDelayTan).sample(); 55 | } 56 | 57 | /** 58 | * Returns the current fake delay after applying random jitter. 59 | */ 60 | public long getJitteredFakeTestDelay() { 61 | return new PoissonDistribution(fakeDelayTest).sample(); 62 | } 63 | 64 | /** 65 | * Returns the current fake delay after applying random jitter. 66 | */ 67 | public long getJitteredFakeTokenDelay() { 68 | return new PoissonDistribution(fakeDelayToken).sample(); 69 | } 70 | 71 | /** 72 | * Updates the moving average for the request duration for the Tan Endpoint with the specified value. 73 | */ 74 | public void updateFakeTanRequestDelay(long realRequestDuration) { 75 | final long currentDelay = fakeDelayTan; 76 | fakeDelayTan = currentDelay + (realRequestDuration - currentDelay) / movingAverageSampleSize; 77 | } 78 | 79 | /** 80 | * Updates the moving average for the request duration for the Tan Endpoint with the specified value. 81 | */ 82 | public void updateFakeTestRequestDelay(long realRequestDuration) { 83 | final long currentDelay = fakeDelayTest; 84 | fakeDelayTan = currentDelay + (realRequestDuration - currentDelay) / movingAverageSampleSize; 85 | } 86 | 87 | /** 88 | * Updates the moving average for the request duration for the Tan Endpoint with the specified value. 89 | */ 90 | public void updateFakeTokenRequestDelay(long realRequestDuration) { 91 | final long currentDelay = fakeDelayToken; 92 | fakeDelayTan = currentDelay + (realRequestDuration - currentDelay) / movingAverageSampleSize; 93 | } 94 | 95 | /** 96 | * Returns the current fake delay in seconds. Used for monitoring. 97 | */ 98 | public Double getFakeTanDelayInSeconds() { 99 | return fakeDelayTan / 1000.; 100 | } 101 | 102 | /** 103 | * Returns the current fake delay in seconds. Used for monitoring. 104 | */ 105 | public Double getFakeTestDelayInSeconds() { 106 | return fakeDelayTest / 1000.; 107 | } 108 | 109 | /** 110 | * Returns the current fake delay in seconds. Used for monitoring. 111 | */ 112 | public Double getFakeTokenDelayInSeconds() { 113 | return fakeDelayToken / 1000.; 114 | } 115 | 116 | /** 117 | * Returns the longest fake delay jittered in milliseconds. 118 | * @return longest jittered 119 | */ 120 | public long getLongestJitter() { 121 | if ((fakeDelayTan > fakeDelayTest) && (fakeDelayTan > fakeDelayToken)) { 122 | return getJitteredFakeTanDelay(); 123 | } else if ((fakeDelayToken > fakeDelayTest) && (fakeDelayToken > fakeDelayTan)) { 124 | return getJitteredFakeTokenDelay(); 125 | } else { 126 | return getJitteredFakeTestDelay(); 127 | } 128 | } 129 | 130 | /** 131 | * Returns the longest fake delay minus average time for Tan in milliseconds. 132 | * @return delay for TAN 133 | */ 134 | public long realDelayTan() { 135 | return (getLongestJitter() - getJitteredFakeTanDelay()); 136 | } 137 | 138 | /** 139 | * Returns the longest fake delay minus average time for RegistrationToken in milliseconds. 140 | * @return delay for RegistrationToken 141 | */ 142 | public long realDelayToken() { 143 | return (getLongestJitter() - getJitteredFakeTokenDelay()); 144 | } 145 | 146 | /** 147 | * Returns the longest fake delay minus average time for TestResult in milliseconds. 148 | * @return delay for TestResult 149 | */ 150 | public long realDelayTest() { 151 | return (getLongestJitter() - getJitteredFakeTestDelay()); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/FakeRequestService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 24 | 25 | import app.coronawarn.verification.model.LabTestResult; 26 | import app.coronawarn.verification.model.RegistrationToken; 27 | import app.coronawarn.verification.model.RegistrationTokenRequest; 28 | import app.coronawarn.verification.model.Tan; 29 | import app.coronawarn.verification.model.TestResult; 30 | import jakarta.validation.Valid; 31 | import java.time.LocalDateTime; 32 | import java.time.ZoneOffset; 33 | import java.util.UUID; 34 | import java.util.concurrent.Executors; 35 | import java.util.concurrent.ScheduledExecutorService; 36 | import lombok.NonNull; 37 | import lombok.RequiredArgsConstructor; 38 | import lombok.extern.slf4j.Slf4j; 39 | import org.apache.commons.lang3.RandomStringUtils; 40 | import org.springframework.http.HttpStatus; 41 | import org.springframework.http.ResponseEntity; 42 | import org.springframework.stereotype.Service; 43 | import org.springframework.web.bind.annotation.RequestBody; 44 | import org.springframework.web.context.request.async.DeferredResult; 45 | 46 | 47 | /** 48 | * This Service generates the fake responses for the Endpoints. 49 | */ 50 | @Slf4j 51 | @Service 52 | @RequiredArgsConstructor 53 | public class FakeRequestService { 54 | 55 | @NonNull 56 | private final FakeDelayService fakeDelayService; 57 | 58 | private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4); 59 | 60 | private static final Integer TEST_RESPONSE_PADDING_LENGTH = 45; 61 | private static final Integer TESTRESULT_RESULT_PADDING = 1; 62 | private static final Integer TAN_RESPONSE_PADDING_LENGTH = 15; 63 | 64 | 65 | /** 66 | * This method generates a fake transaction number by a Registration Token, if the state of the COVID-19 lab-test is 67 | * positive. 68 | * 69 | * @param registrationToken generated by a hashed guid or a teleTAN. {@link RegistrationToken} 70 | * @return A generated transaction number {@link Tan}. 71 | */ 72 | public DeferredResult> generateTan(@Valid @RequestBody RegistrationToken registrationToken) { 73 | long delay = fakeDelayService.getLongestJitter(); 74 | DeferredResult> deferredResult = new DeferredResult<>(); 75 | Tan returnTan = new Tan(UUID.randomUUID().toString(), 76 | RandomStringUtils.randomAlphanumeric(TAN_RESPONSE_PADDING_LENGTH)); 77 | scheduledExecutor.schedule(() -> deferredResult.setResult(ResponseEntity.status(HttpStatus.CREATED) 78 | .body(returnTan)), delay, MILLISECONDS); 79 | return deferredResult; 80 | } 81 | 82 | 83 | /** 84 | * This method generates a fake registrationToken by a hashed guid or a teleTAN. 85 | * 86 | * @param request {@link RegistrationTokenRequest} 87 | * @return RegistrationToken - the created registration token {@link RegistrationToken} 88 | */ 89 | public DeferredResult> generateRegistrationToken( 90 | @RequestBody @Valid RegistrationTokenRequest request) { 91 | long delay = fakeDelayService.getLongestJitter(); 92 | DeferredResult> deferredResult = new DeferredResult<>(); 93 | scheduledExecutor.schedule(() -> deferredResult.setResult(ResponseEntity.status(HttpStatus.CREATED) 94 | .body(new RegistrationToken(UUID.randomUUID().toString(), 95 | RandomStringUtils.randomAlphanumeric(TESTRESULT_RESULT_PADDING)))), delay, MILLISECONDS); 96 | return deferredResult; 97 | } 98 | 99 | 100 | /** 101 | * Returns the fake test status of the COVID-19 test. 102 | * 103 | * @param registrationToken generated by a hashed guid {@link RegistrationToken} 104 | * @return the test result / status of the COVID-19 test, which can be POSITIVE, NEGATIVE, INVALID, PENDING or FAILED 105 | * and will always be POSITIVE for a TeleTan. 106 | */ 107 | public DeferredResult> getTestState( 108 | @Valid @RequestBody RegistrationToken registrationToken) { 109 | long delay = fakeDelayService.getLongestJitter(); 110 | DeferredResult> deferredResult = new DeferredResult<>(); 111 | scheduledExecutor.schedule(() -> deferredResult.setResult(ResponseEntity 112 | .ok(new TestResult(LabTestResult.POSITIVE.getTestResult(), LocalDateTime.now().toEpochSecond(ZoneOffset.UTC), 113 | null, RandomStringUtils.randomAlphanumeric(TEST_RESPONSE_PADDING_LENGTH)))), delay, MILLISECONDS); 114 | return deferredResult; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/HashingService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | import lombok.extern.slf4j.Slf4j; 26 | import org.apache.commons.codec.digest.DigestUtils; 27 | import org.springframework.stereotype.Component; 28 | 29 | /** 30 | * This class represents the hashing service for providing and check a hash string. 31 | */ 32 | @Slf4j 33 | @Component 34 | public class HashingService { 35 | 36 | private static final String GUID_HASH_PATTERN = "^[0-9A-Fa-f]{64}$"; 37 | private static final Pattern PATTERN = Pattern.compile(GUID_HASH_PATTERN); 38 | 39 | /** 40 | * Calculates the SHA-256 digest and returns the value as a hex string. 41 | * 42 | * @param toHash that will be Hashed 43 | * @return the hash of the supplied string 44 | */ 45 | public String hash(String toHash) { 46 | log.debug("Hash process has been called."); 47 | return DigestUtils.sha256Hex(toHash); 48 | } 49 | 50 | /** 51 | * Calculates the SHA-256 digest and returns an check digit. 52 | * 53 | * @param toHash that will be Hashed 54 | * @return the check digit 55 | */ 56 | public String getCheckDigit(String toHash) { 57 | log.info("get check digit process has been called."); 58 | return DigestUtils.sha256Hex(toHash).substring(0, 1).toUpperCase().replace("0", "G").replace("1", "H"); 59 | } 60 | 61 | /** 62 | * Returns true if the String is resembles a SHA256 Pattern. 63 | * 64 | * @param toValidate String that will be checked to match the pattern of a SHA256 Hash 65 | * @return Boolean if the String Matches the Pattern 66 | */ 67 | public boolean isHashValid(String toValidate) { 68 | Matcher matcher = PATTERN.matcher(toValidate); 69 | boolean matches = matcher.matches(); 70 | if (!matches) { 71 | log.warn("The hashed guid has no valid pattern"); 72 | } 73 | return matches; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/JwtService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import app.coronawarn.verification.client.IamClient; 24 | import app.coronawarn.verification.config.VerificationApplicationConfig; 25 | import app.coronawarn.verification.model.AuthorizationRole; 26 | import app.coronawarn.verification.model.Certs; 27 | import app.coronawarn.verification.model.Key; 28 | import io.jsonwebtoken.Claims; 29 | import io.jsonwebtoken.JwtException; 30 | import io.jsonwebtoken.Jwts; 31 | import java.io.ByteArrayInputStream; 32 | import java.io.InputStream; 33 | import java.security.PublicKey; 34 | import java.security.cert.CertificateException; 35 | import java.security.cert.CertificateFactory; 36 | import java.security.cert.X509Certificate; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import java.util.Map; 40 | import java.util.function.Function; 41 | import lombok.NonNull; 42 | import lombok.RequiredArgsConstructor; 43 | import lombok.extern.slf4j.Slf4j; 44 | import org.springframework.stereotype.Component; 45 | 46 | /** 47 | * This class represents the JWT service for token authorization and validation. 48 | */ 49 | @Slf4j 50 | @RequiredArgsConstructor 51 | @Component 52 | public class JwtService { 53 | 54 | /** 55 | * The bearer prefix for the json web token. 56 | */ 57 | public static final String TOKEN_PREFIX = "Bearer "; 58 | /** 59 | * The certificate begin prefix. 60 | */ 61 | public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; 62 | /** 63 | * The certificate end suffix. 64 | */ 65 | public static final String END_CERT = "-----END CERTIFICATE-----"; 66 | /** 67 | * The http request header name for JWT 'Authorization'. 68 | */ 69 | public static final String HEADER_NAME_AUTHORIZATION = "Authorization"; 70 | 71 | private static final String ROLES = "roles"; 72 | private static final String REALM_ACCESS = "realm_access"; 73 | 74 | @NonNull 75 | private final IamClient iamClient; 76 | 77 | @NonNull 78 | private final VerificationApplicationConfig verificationApplicationConfig; 79 | 80 | /** 81 | * Validates the given token is given, the token starts with the needed prefix, the signing key is not null and the 82 | * token is valid. 83 | * 84 | * @param authorizationToken The authorization token to validate 85 | * @param mandatoryRoles list of roles which are required to pass 86 | * @return true, if the token is valid, otherwise false 87 | */ 88 | public boolean isAuthorized(String authorizationToken, List mandatoryRoles) { 89 | // check if the JWT is enabled 90 | if (!verificationApplicationConfig.getJwt().getEnabled()) { 91 | return true; 92 | } 93 | if (null != authorizationToken && authorizationToken.startsWith(TOKEN_PREFIX)) { 94 | String jwtToken = authorizationToken.substring(TOKEN_PREFIX.length()); 95 | return validateToken(jwtToken, getPublicKey(), mandatoryRoles); 96 | } 97 | return false; 98 | } 99 | 100 | /** 101 | * Validates the given token. If one of the given roles {@link AuthorizationRole} exists and verified by a public key 102 | * 103 | * @param token The authorization token to validate 104 | * @param publicKey the key from the IAM server 105 | * @param mandatoryRoles List of roles which are required to pass. 106 | * @return true, if the token is valid, otherwise false 107 | */ 108 | public boolean validateToken(final String token, final PublicKey publicKey, List mandatoryRoles) { 109 | log.debug("process validateToken() by - token: {} PK: {}", token, publicKey); 110 | if (null != publicKey) { 111 | try { 112 | List roleNames = getRoles(token, publicKey); 113 | 114 | // Return false if one of the mandatory roles are not present 115 | for (AuthorizationRole mandatoryRole : mandatoryRoles) { 116 | if (!roleNames.contains(mandatoryRole.getRoleName())) { 117 | return false; 118 | } 119 | } 120 | 121 | // Return true if at least one of the authorization roles are present 122 | AuthorizationRole[] roles = AuthorizationRole.values(); 123 | for (AuthorizationRole role : roles) { 124 | if (roleNames.contains(role.getRoleName())) { 125 | return true; 126 | } 127 | } 128 | } catch (JwtException ex) { 129 | log.warn("Token is not valid: {}.", ex.getMessage()); 130 | return false; 131 | } 132 | } 133 | log.warn("No public key for Token validation found."); 134 | return false; 135 | } 136 | 137 | public String getSubject(final String token, final PublicKey publicKey) { 138 | return getClaimFromToken(token, Claims::getSubject, publicKey); 139 | } 140 | 141 | private List getRoles(final String token, final PublicKey publicKey) { 142 | Map> realm = getRealmFromToken(token, publicKey); 143 | return realm.getOrDefault(ROLES, new ArrayList<>()); 144 | } 145 | 146 | @SuppressWarnings("unchecked") 147 | private Map> getRealmFromToken(final String token, final PublicKey publicKey) { 148 | final Claims claims = getAllClaimsFromToken(token, publicKey); 149 | return claims.get(REALM_ACCESS, Map.class); 150 | } 151 | 152 | public T getClaimFromToken(final String token, Function claimsResolver, final PublicKey publicKey) { 153 | final Claims claims = getAllClaimsFromToken(token, publicKey); 154 | return claimsResolver.apply(claims); 155 | } 156 | 157 | private Claims getAllClaimsFromToken(final String token, final PublicKey publicKey) { 158 | return Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody(); 159 | } 160 | 161 | /** 162 | * Get the certificate from IAM client. 163 | * As long as Keycloak can rotate it’s keys we decided to reload 164 | * the key on every validateToken call especially the method call 165 | * is fortunately limited in time and number too. 166 | * 167 | * @return the calculated Public key from the certificate 168 | */ 169 | public PublicKey getPublicKey() { 170 | Certs certs = iamClient.certs(); 171 | log.debug("process getPublicKey() - cert info from IAM certs: {}", certs); 172 | for (Key key : certs.getKeys()) { 173 | if (key.isCertValid()) { 174 | String certb64 = key.getX5c().get(0); 175 | String wrappedCert = BEGIN_CERT + System.lineSeparator() + certb64 + System.lineSeparator() + END_CERT; 176 | try { 177 | byte[] certBytes = wrappedCert.getBytes(java.nio.charset.StandardCharsets.UTF_8); 178 | CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 179 | InputStream in = new ByteArrayInputStream(certBytes); 180 | X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in); 181 | return certificate.getPublicKey(); 182 | } catch (CertificateException ex) { 183 | log.warn("Error generate certificate: {}.", ex.getMessage()); 184 | } 185 | } else { 186 | log.warn("Wrong use or alg key given! use: {} alg: {}", key.getUse(), key.getAlg()); 187 | log.warn("Keys use: {} and alg: {} are expected!", Key.SIG, Key.RS256); 188 | } 189 | } 190 | return null; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/service/TestResultServerService.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import app.coronawarn.verification.client.TestResultServerClient; 24 | import app.coronawarn.verification.model.HashedGuid; 25 | import app.coronawarn.verification.model.TestResult; 26 | import lombok.RequiredArgsConstructor; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.springframework.stereotype.Component; 29 | 30 | /** 31 | * This class represents the lab server service. 32 | */ 33 | @Slf4j 34 | @RequiredArgsConstructor 35 | @Component 36 | public class TestResultServerService { 37 | 38 | private final TestResultServerClient testResultServerClient; 39 | 40 | /** 41 | * This method gives an TestResult for a guid. 42 | * 43 | * @param guid hashed GUID 44 | * @return Testresult for GUID 45 | */ 46 | public TestResult result(HashedGuid guid) { 47 | return testResultServerClient.result(guid); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/validator/RegistrationTokenKeyConstraint.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.validator; 22 | 23 | import static java.lang.annotation.ElementType.TYPE; 24 | 25 | import jakarta.validation.Constraint; 26 | import jakarta.validation.Payload; 27 | import java.lang.annotation.Documented; 28 | import java.lang.annotation.Retention; 29 | import java.lang.annotation.RetentionPolicy; 30 | import java.lang.annotation.Target; 31 | 32 | 33 | /** 34 | * The validation-annotation for a registration token request. 35 | */ 36 | @Documented 37 | @Constraint(validatedBy = RegistrationTokenRequestValidator.class) 38 | @Target({TYPE}) 39 | @Retention(RetentionPolicy.RUNTIME) 40 | public @interface RegistrationTokenKeyConstraint { 41 | 42 | /** 43 | * The default key for creating error messages in case the constraint is violated. 44 | */ 45 | String message() default "The key is not valid"; 46 | 47 | /** 48 | * Allows the specification of validation groups, to which this constraint belongs. 49 | */ 50 | Class[] groups() default {}; 51 | 52 | /** 53 | * Assigns custom payload objects to a constraint. 54 | */ 55 | Class[] payload() default {}; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/app/coronawarn/verification/validator/RegistrationTokenRequestValidator.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.validator; 22 | 23 | import app.coronawarn.verification.model.RegistrationTokenKeyType; 24 | import app.coronawarn.verification.model.RegistrationTokenRequest; 25 | import app.coronawarn.verification.service.HashingService; 26 | import app.coronawarn.verification.service.TanService; 27 | import jakarta.validation.ConstraintValidator; 28 | import jakarta.validation.ConstraintValidatorContext; 29 | import lombok.NonNull; 30 | import lombok.RequiredArgsConstructor; 31 | import lombok.extern.slf4j.Slf4j; 32 | import org.springframework.stereotype.Component; 33 | 34 | /** 35 | * The registration token request validator. 36 | */ 37 | @Slf4j 38 | @RequiredArgsConstructor 39 | @Component 40 | public class RegistrationTokenRequestValidator 41 | implements ConstraintValidator { 42 | /** 43 | * The {@link HashingService}. 44 | */ 45 | @NonNull 46 | private final HashingService hashingService; 47 | 48 | /** 49 | * The {@link TanService}. 50 | */ 51 | @NonNull 52 | private final TanService tanService; 53 | 54 | @Override 55 | public boolean isValid(RegistrationTokenRequest request, ConstraintValidatorContext arg1) { 56 | 57 | String key = request.getKey(); 58 | RegistrationTokenKeyType keyType = request.getKeyType(); 59 | if (key == null || keyType == null) { 60 | return false; 61 | } 62 | return switch (keyType) { 63 | case GUID -> hashingService.isHashValid(key); 64 | case TELETAN -> tanService.verifyTeleTan(key); 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/application-cloud.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: org.postgresql.Driver 4 | url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE} 5 | username: ${POSTGRESQL_USER} 6 | password: ${POSTGRESQL_PASSWORD} 7 | jpa: 8 | database-platform: org.hibernate.dialect.PostgreSQLDialect 9 | server: 10 | ssl: 11 | hostname-verify: false 12 | key-store: ${SSL_VERIFICATION_KEYSTORE_PATH} 13 | key-store-password: ${SSL_VERIFICATION_KEYSTORE_PASSWORD} 14 | trust-store: ${SSL_VERIFICATION_TRUSTSTORE_PATH} 15 | trust-store-password: ${SSL_VERIFICATION_TRUSTSTORE_PASSWORD} 16 | cwa-testresult-server: 17 | ssl: 18 | enabled: true 19 | one-way: true 20 | two-way: true 21 | hostname-verify: false 22 | key-store: ${SSL_VERIFICATION_KEYSTORE_PATH} 23 | key-store-password: ${SSL_VERIFICATION_KEYSTORE_PASSWORD} 24 | trust-store: ${SSL_TESTRESULTSERVER_TRUSTSTORE_PATH} 25 | trust-store-password: ${SSL_TESTRESULTSERVER_TRUSTSTORE_PASSWORD} 26 | disable-dob-hash-check-for-external-test-result: ${DISABLE_DOB_EXTERNAL_TR} 27 | allowed-client-certificates: ${VERIFICATION_ALLOWEDCLIENTCERTIFICATES} 28 | -------------------------------------------------------------------------------- /src/main/resources/application-external.yml: -------------------------------------------------------------------------------- 1 | server: 2 | ssl: 3 | protocol: TLS 4 | enabled-protocols: TLSv1.3,TLSv1.2 5 | ciphers: >- 6 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 7 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 8 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 9 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 10 | TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 11 | TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 12 | TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 13 | TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 14 | TLS_AES_128_GCM_SHA256 15 | TLS_AES_256_GCM_SHA384 16 | TLS_AES_128_CCM_SHA256 17 | -------------------------------------------------------------------------------- /src/main/resources/application-internal.yml: -------------------------------------------------------------------------------- 1 | server: 2 | ssl: 3 | protocol: TLS 4 | enabled-protocols: TLSv1.3 5 | -------------------------------------------------------------------------------- /src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | cwa-testresult-server: 2 | url: http://localhost:8088 3 | ssl: 4 | enabled: false 5 | one-way: false 6 | two-way: false 7 | hostname-verify: false 8 | key-store: classpath:keystore.jks 9 | key-store-password: changeit 10 | trust-store: classpath:truststore.jks 11 | trust-store-password: changeit 12 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: cwa-verification-server 4 | datasource: 5 | driver-class-name: org.h2.Driver 6 | url: jdbc:h2:mem:verification 7 | username: sa 8 | password: '' 9 | jpa: 10 | database-platform: org.hibernate.dialect.H2Dialect 11 | hibernate: 12 | ddl-auto: validate 13 | liquibase: 14 | change-log: classpath:db/changelog.yml 15 | server: 16 | max-post-size: 10000 17 | feign: 18 | client: 19 | config: 20 | default: 21 | connect-timeout: 5000 22 | read-timeout: 5000 23 | logger-level: basic 24 | jwt: 25 | server: http://localhost:8080 26 | enabled: false 27 | springdoc: 28 | api-docs: 29 | path: /api/docs 30 | swagger-ui: 31 | path: /api/swagger 32 | management: 33 | server: 34 | ssl: 35 | enabled: false 36 | port: 8081 37 | endpoint: 38 | info: 39 | enabled: true 40 | health: 41 | enabled: true 42 | metrics: 43 | enabled: true 44 | prometheus: 45 | enabled: true 46 | endpoints: 47 | enabled-by-default: false 48 | web: 49 | exposure: 50 | include: info,health,metrics,prometheus 51 | jmx: 52 | exposure: 53 | include: info,health,metrics,prometheus 54 | health: 55 | probes: 56 | enabled: true 57 | metrics: 58 | export: 59 | prometheus: 60 | enabled: true 61 | tan: 62 | tele: 63 | ratelimiting: 64 | count: 1000 65 | seconds: 3600 66 | threshold-in-percent: 80 67 | valid: 68 | length: 9 69 | hours: 1 70 | eventDays: 2 71 | valid: 72 | days: 14 73 | appsession: 74 | tancountermax: 1 75 | entities: 76 | cleanup: 77 | cron: "0 1 * * * *" 78 | days: 21 79 | initialFakeDelayMilliseconds: 10 80 | fakeDelayMovingAverageSamples: 5 81 | request: 82 | sizelimit: 10000 83 | 84 | cwa-testresult-server: 85 | url: http://localhost:8088 86 | allowed-client-certificates: 87 | -------------------------------------------------------------------------------- /src/main/resources/bootstrap-cloud.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application: 4 | name: cwa-verification-server 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: 'cwa-verification-server' 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 | -------------------------------------------------------------------------------- /src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | cloud: 4 | vault: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - include: 3 | file: changelog/v000-create-app-session-table.yml 4 | relativeToChangelogFile: true 5 | - include: 6 | file: changelog/v000-create-tan-table.yml 7 | relativeToChangelogFile: true 8 | - include: 9 | file: changelog/v001-add-dob-hash-column.yml 10 | relativeToChangelogFile: true 11 | - include: 12 | file: changelog/v002-add-teletan-type-column.yml 13 | relativeToChangelogFile: true 14 | - include: 15 | file: changelog/v003-add-unique-hashed-guid.yml 16 | relativeToChangelogFile: true 17 | - include: 18 | file: changelog/v004-add-unique-registration-token-teletan.yml 19 | relativeToChangelogFile: true 20 | - include: 21 | file: changelog/v005-add-shedlock.yml 22 | relativeToChangelogFile: true 23 | - include: 24 | file: changelog/v006-add-index-dob-hashed-guid.yml 25 | relativeToChangelogFile: true 26 | - include: 27 | file: changelog/v007-add-seperate-unique-constraints-for-hashed-guid.yml 28 | relativeToChangelogFile: true 29 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v000-create-app-session-table.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: create-app-session-table 4 | author: jhagestedt 5 | changes: 6 | - createTable: 7 | tableName: app_session 8 | columns: 9 | - column: 10 | name: id 11 | type: bigint 12 | constraints: 13 | unique: true 14 | nullable: false 15 | primaryKey: true 16 | - column: 17 | name: version 18 | type: bigint 19 | constraints: 20 | nullable: false 21 | - column: 22 | name: created_at 23 | type: datetime 24 | constraints: 25 | nullable: false 26 | - column: 27 | name: updated_at 28 | type: datetime 29 | constraints: 30 | nullable: false 31 | - column: 32 | name: hashed_guid 33 | type: varchar(64) 34 | - column: 35 | name: registration_token_hash 36 | type: varchar(64) 37 | - column: 38 | name: tele_tan_hash 39 | type: varchar(64) 40 | - column: 41 | name: tan_counter 42 | type: int 43 | - column: 44 | name: sot 45 | type: varchar(255) 46 | - changeSet: 47 | id: create-app-session-table-increment 48 | author: jhagestedt 49 | changes: 50 | - addAutoIncrement: 51 | tableName: app_session 52 | columnName: id 53 | columnDataType: bigint 54 | startWith: 1 55 | incrementBy: 1 56 | - changeSet: 57 | id: create-app-session-table-indexes 58 | author: jhagestedt 59 | changes: 60 | - createIndex: 61 | tableName: app_session 62 | indexName: idx_app_session_guid_hash 63 | columns: 64 | - column: 65 | name: hashed_guid 66 | type: varchar(64) 67 | - createIndex: 68 | tableName: app_session 69 | indexName: idx_app_session_registration_token_hash 70 | columns: 71 | - column: 72 | name: registration_token_hash 73 | type: varchar(64) 74 | - createIndex: 75 | tableName: app_session 76 | indexName: idx_app_session_tele_tan_hash 77 | columns: 78 | - column: 79 | name: tele_tan_hash 80 | type: varchar(64) 81 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v000-create-tan-table.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: create-tan-table 4 | author: jhagestedt 5 | changes: 6 | - createTable: 7 | tableName: tan 8 | columns: 9 | - column: 10 | name: id 11 | type: bigint 12 | constraints: 13 | unique: true 14 | nullable: false 15 | primaryKey: true 16 | - column: 17 | name: version 18 | type: bigint 19 | constraints: 20 | nullable: false 21 | - column: 22 | name: created_at 23 | type: datetime 24 | constraints: 25 | nullable: false 26 | - column: 27 | name: updated_at 28 | type: datetime 29 | constraints: 30 | nullable: false 31 | - column: 32 | name: valid_from 33 | type: datetime 34 | constraints: 35 | nullable: false 36 | - column: 37 | name: valid_until 38 | type: datetime 39 | constraints: 40 | nullable: false 41 | - column: 42 | name: tan_hash 43 | type: varchar(64) 44 | - column: 45 | name: sot 46 | type: varchar(255) 47 | - column: 48 | name: type 49 | type: varchar(255) 50 | - column: 51 | name: redeemed 52 | type: boolean 53 | - changeSet: 54 | id: create-tan-table-increment 55 | author: jhagestedt 56 | changes: 57 | - addAutoIncrement: 58 | tableName: tan 59 | columnName: id 60 | columnDataType: bigint 61 | startWith: 1 62 | incrementBy: 1 63 | - changeSet: 64 | id: create-tan-table-indexes 65 | author: jhagestedt 66 | changes: 67 | - createIndex: 68 | tableName: tan 69 | indexName: idx_tan_tan_hash 70 | columns: 71 | - column: 72 | name: tan_hash 73 | type: varchar(64) 74 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v001-add-dob-hash-column.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-dob-hash-column 4 | author: f11h 5 | changes: 6 | - addColumn: 7 | tableName: app_session 8 | columns: 9 | name: hashed_guid_dob 10 | type: varchar(64) 11 | constraints: 12 | nullable: true 13 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v002-add-teletan-type-column.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-teletan-type-column 4 | author: f11h 5 | changes: 6 | - addColumn: 7 | tableName: tan 8 | columns: 9 | name: teletan_type 10 | type: varchar(10) 11 | constraints: 12 | nullable: true 13 | - addColumn: 14 | tableName: app_session 15 | columns: 16 | name: teletan_type 17 | type: varchar(10) 18 | constraints: 19 | nullable: true 20 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v003-add-unique-hashed-guid.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-unique-hashed-guid 4 | author: f11h 5 | changes: 6 | - addUniqueConstraint: 7 | tableName: app_session 8 | columnNames: hashed_guid, hashed_guid_dob 9 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v004-add-unique-registration-token-teletan.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-unique-registration-token-teletan 4 | author: f11h 5 | changes: 6 | - addUniqueConstraint: 7 | tableName: app_session 8 | columnNames: registration_token_hash, tele_tan_hash 9 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v005-add-shedlock.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-shedlock 4 | author: mschulte-tsi 5 | changes: 6 | - createTable: 7 | tableName: shedlock 8 | columns: 9 | - column: 10 | name: name 11 | type: varchar(64) 12 | constraints: 13 | nullable: false 14 | primaryKey: true 15 | - column: 16 | name: lock_until 17 | type: datetime(2) 18 | constraints: 19 | nullable: false 20 | - column: 21 | name: locked_at 22 | type: datetime(2) 23 | constraints: 24 | nullable: false 25 | - column: 26 | name: locked_by 27 | type: varchar(255) 28 | constraints: 29 | nullable: false 30 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v006-add-index-dob-hashed-guid.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-unique-hashed-guid 4 | author: mschulte-tsi 5 | changes: 6 | - createIndex: 7 | tableName: app_session 8 | indexName: idx_app_session_hashed_guid_dob 9 | columns: 10 | - column: 11 | name: hashed_guid_dob 12 | type: varchar(64) 13 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/v007-add-seperate-unique-constraints-for-hashed-guid.yml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: add-seperate-unique-constraints-for-hashed-guid 4 | author: f11h 5 | changes: 6 | - addUniqueConstraint: 7 | tableName: app_session 8 | columnNames: hashed_guid 9 | - addUniqueConstraint: 10 | tableName: app_session 11 | columnNames: hashed_guid_dob 12 | -------------------------------------------------------------------------------- /src/test/java/app/coronawarn/verification/TestUtils.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification; 22 | 23 | import app.coronawarn.verification.domain.VerificationAppSession; 24 | import app.coronawarn.verification.domain.VerificationTan; 25 | import app.coronawarn.verification.model.AppSessionSourceOfTrust; 26 | import app.coronawarn.verification.model.AuthorizationRole; 27 | import app.coronawarn.verification.model.TanSourceOfTrust; 28 | import app.coronawarn.verification.model.TanType; 29 | import app.coronawarn.verification.model.TeleTanType; 30 | import app.coronawarn.verification.model.TestResult; 31 | import app.coronawarn.verification.repository.VerificationAppSessionRepository; 32 | import com.fasterxml.jackson.core.JsonProcessingException; 33 | import com.fasterxml.jackson.databind.ObjectMapper; 34 | import io.jsonwebtoken.Jwts; 35 | import io.jsonwebtoken.SignatureAlgorithm; 36 | import java.security.PrivateKey; 37 | import java.time.Instant; 38 | import java.time.LocalDateTime; 39 | import java.util.ArrayList; 40 | import java.util.Date; 41 | import java.util.HashMap; 42 | import java.util.List; 43 | import java.util.Map; 44 | import org.springframework.beans.factory.annotation.Autowired; 45 | import org.springframework.stereotype.Component; 46 | 47 | @Component 48 | public class TestUtils { 49 | 50 | static final String TEST_GUI_HASH = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; 51 | static final String TEST_GUI_HASH2 = "a1f7347308703928470938247903247903274903274903297041bea091d14d4d"; 52 | static final String TEST_GUI_HASH_DOB = "x0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; 53 | static final String TEST_GUI_HASH_DOB2 = "x1f7347308703928470938247903247903274903274903297041bea091d14d4d"; 54 | static final String RESULT_PADDING = ""; 55 | static final String LAB_ID = "l".repeat(64); 56 | static final String TEST_INVALID_GUI_HASH = "f0e4c2f76c58916ec2b"; 57 | static final String TEST_TELE_TAN = "R3ZNUeV"; 58 | static final String TEST_TELE_TAN_HASH = "eeaa54dc40aa84f587e3bc0cbbf18f7c05891558a5fe1348d52f3277794d8730"; 59 | static final String TEST_INVALID_REG_TOK = "1234567890"; 60 | static final String TEST_REG_TOK = "1ea6ce8a-9740-41ea-bb37-0242ac130002"; 61 | static final String TEST_REG_TOK_HASH = "0199effab87800689c15c08e234db54f088cc365132ffc230e882b82cd3ecf95"; 62 | static final TestResult TEST_LAB_POSITIVE_RESULT = new TestResult(2,0, LAB_ID, null); 63 | static final TestResult QUICK_TEST_POSITIVE_RESULT = new TestResult(7,0, LAB_ID, null); 64 | static final TestResult TEST_LAB_NEGATIVE_RESULT = new TestResult(1,0, LAB_ID, null); 65 | static final String TEST_TAN = "1819d933-45f6-4e3c-80c7-eeffd2d44ee6"; 66 | static final String TEST_INVALID_TAN = "1ea6ce8a-9740-11ea-is-invalid"; 67 | static final TanSourceOfTrust TEST_SOT = TanSourceOfTrust.CONNECTED_LAB; 68 | static final String TEST_HASHED_TAN = "cfb5368fc0fca485847acb28e6a96c958bb6ab7350ac766be88ad13841750231"; 69 | static final TanType TEST_TAN_TYPE = TanType.TAN; 70 | static final LocalDateTime TAN_VALID_UNTIL_IN_DAYS = LocalDateTime.now().plusDays(7); 71 | static final String PREFIX_API_VERSION = "/version/v1"; 72 | static final String REGISTRATION_TOKEN_URI = "/registrationToken"; 73 | static final String TAN_VERIFICATION_URI = "/tan/verify"; 74 | 75 | private static ObjectMapper objectMapper; 76 | 77 | @Autowired 78 | public TestUtils(ObjectMapper objectMapper) { 79 | TestUtils.objectMapper = objectMapper; 80 | } 81 | 82 | static void prepareAppSessionTestData(VerificationAppSessionRepository appSessionRepository) { 83 | appSessionRepository.deleteAll(); 84 | appSessionRepository.save(getAppSessionTestData()); 85 | } 86 | 87 | static void prepareAppSessionTestDataDob(VerificationAppSessionRepository appSessionRepository) { 88 | appSessionRepository.deleteAll(); 89 | appSessionRepository.save(getAppSessionTestData(AppSessionSourceOfTrust.HASHED_GUID, true)); 90 | } 91 | 92 | static void prepareAppSessionTestDataSotTeleTan(VerificationAppSessionRepository appSessionRepository) { 93 | appSessionRepository.deleteAll(); 94 | appSessionRepository.save(getAppSessionTestData(AppSessionSourceOfTrust.TELETAN, false)); 95 | } 96 | 97 | static VerificationAppSession getAppSessionTestData(AppSessionSourceOfTrust sot, boolean dob) { 98 | VerificationAppSession cv = new VerificationAppSession(); 99 | cv.setTeleTanType(TeleTanType.EVENT); 100 | cv.setHashedGuid(TEST_GUI_HASH); 101 | cv.setHashedGuidDob(dob ? TEST_GUI_HASH_DOB : null); 102 | cv.setCreatedAt(LocalDateTime.now()); 103 | cv.setUpdatedAt(LocalDateTime.now()); 104 | cv.setTanCounter(0); 105 | cv.setSourceOfTrust(sot); 106 | cv.setRegistrationTokenHash(TEST_REG_TOK_HASH); 107 | return cv; 108 | } 109 | 110 | static VerificationAppSession getAppSessionTestData() { 111 | return getAppSessionTestData(AppSessionSourceOfTrust.HASHED_GUID, false); 112 | } 113 | 114 | static VerificationTan getTeleTanTestData() { 115 | VerificationTan cvtan = new VerificationTan(); 116 | cvtan.setTeleTanType(TeleTanType.EVENT); 117 | cvtan.setCreatedAt(LocalDateTime.now()); 118 | cvtan.setUpdatedAt(LocalDateTime.now()); 119 | cvtan.setRedeemed(false); 120 | cvtan.setSourceOfTrust(TanSourceOfTrust.TELETAN); 121 | cvtan.setTanHash(TEST_HASHED_TAN); 122 | cvtan.setType(TanType.TELETAN); 123 | cvtan.setValidFrom(LocalDateTime.now()); 124 | cvtan.setValidUntil(LocalDateTime.now().plusHours(1)); 125 | return cvtan; 126 | } 127 | 128 | static VerificationTan getVerificationTANTestData() { 129 | VerificationTan cvtan = new VerificationTan(); 130 | cvtan.setTeleTanType(TeleTanType.EVENT); 131 | cvtan.setCreatedAt(LocalDateTime.now()); 132 | cvtan.setUpdatedAt(LocalDateTime.now()); 133 | cvtan.setRedeemed(false); 134 | cvtan.setSourceOfTrust(TEST_SOT); 135 | cvtan.setTanHash(TEST_HASHED_TAN); 136 | cvtan.setType(TEST_TAN_TYPE); 137 | cvtan.setValidFrom(LocalDateTime.now().minusDays(5)); 138 | cvtan.setValidUntil(TAN_VALID_UNTIL_IN_DAYS); 139 | return cvtan; 140 | } 141 | 142 | static String getAsJsonFormat(Object o) throws JsonProcessingException { 143 | return objectMapper.writeValueAsString(o); 144 | } 145 | 146 | static String getJwtTestData(final long expirationSecondsToAdd, PrivateKey privateKey, AuthorizationRole... roles) { 147 | final Map> realmAccessMap = new HashMap<>(); 148 | final List roleNames = new ArrayList<>(); 149 | for (AuthorizationRole role : roles) { 150 | roleNames.add(role.getRoleName()); 151 | } 152 | 153 | realmAccessMap.put("roles", roleNames); 154 | 155 | return Jwts.builder() 156 | .setExpiration(Date.from(Instant.now().plusSeconds(expirationSecondsToAdd))) 157 | .setIssuedAt(Date.from(Instant.now())) 158 | .setId("baeaa733-521e-4d2e-8abe-95bb440a9f5f") 159 | .setIssuer("http://localhost:8080/auth/realms/cwa") 160 | .setAudience("account") 161 | .setSubject("72b3b494-a0f4-49f5-b235-1e9f93c86e58") 162 | .claim("auth_time", "1590742669") 163 | .claim("iss", "http://localhost:8080/auth/realms/cwa") 164 | .claim("aud", "account") 165 | .claim("typ", "Bearer") 166 | .claim("azp", "verification-portal") 167 | .claim("session_state", "41cc4d83-e394-4d08-b887-28d8c5372d4a") 168 | .claim("acr", "0") 169 | .claim("realm_access", realmAccessMap) 170 | .claim("resource_access", new HashMap<>()) 171 | .claim("scope", "openid profile email") 172 | .claim("email_verified", false) 173 | .claim("preferred_username", "test") 174 | .signWith(privateKey, SignatureAlgorithm.RS256) 175 | .compact(); 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/test/java/app/coronawarn/verification/VerificationApplicationExternalTestHttp.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | //package app.coronawarn.verification; 22 | // 23 | //import java.io.BufferedReader; 24 | //import java.io.IOException; 25 | //import java.io.InputStreamReader; 26 | //import java.io.PrintWriter; 27 | //import java.net.InetAddress; 28 | //import java.net.Socket; 29 | // 30 | //import app.coronawarn.verification.config.SecurityConfig; 31 | //import org.junit.Assert; 32 | //import org.junit.Test; 33 | //import org.junit.runner.RunWith; 34 | //import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 35 | //import org.springframework.boot.autoconfigure.kafka.KafkaProperties; 36 | //import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 37 | //import org.springframework.boot.test.context.SpringBootTest; 38 | //import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 39 | //import org.springframework.boot.web.server.LocalServerPort; 40 | //import org.springframework.context.annotation.ComponentScan; 41 | //import org.springframework.context.annotation.FilterType; 42 | //import org.springframework.test.context.junit4.SpringRunner; 43 | // 44 | //@RunWith(SpringRunner.class) 45 | //@SpringBootTest(webEnvironment = RANDOM_PORT) 46 | //@ComponentScan(basePackages = {"app.coronawan.verification"}, 47 | // excludeFilters = {@ComponentScan.Filter( 48 | // type = FilterType.ASSIGNABLE_TYPE, 49 | // value = {SecurityConfig.class}) 50 | // }) 51 | //public class VerificationApplicationExternalTestHttp { 52 | // 53 | // @LocalServerPort 54 | // private int port; 55 | // 56 | // @Test 57 | // public void testChunkedModeIsDenied() throws IOException { 58 | // Socket socket = new Socket(InetAddress.getLocalHost(), port); 59 | // PrintWriter writer = new PrintWriter(socket.getOutputStream()); 60 | // 61 | // writer.print("POST /version/v1/registrationToken HTTP/1.1\r\n"); 62 | // writer.print("Host: 127.0.0.1:" + port + "\r\n"); 63 | // writer.print("Content-Type: application/json\r\n"); 64 | // writer.print("Transfer-Encoding: Chunked\r\n\r\n"); 65 | // writer.flush(); 66 | // 67 | // writer.print("{ \"randomBody\": 42 }"); 68 | // writer.flush(); 69 | // 70 | // BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 71 | // Assert.assertEquals("HTTP/1.1 406", reader.readLine().trim()); 72 | // 73 | // reader.close(); 74 | // socket.close(); 75 | // } 76 | // 77 | // private class SecurityAutoConfiguration { 78 | // } 79 | //} 80 | -------------------------------------------------------------------------------- /src/test/java/app/coronawarn/verification/VerificationApplicationExternalTestWorkaround.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification; 22 | 23 | import static org.mockito.BDDMockito.given; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 26 | 27 | import app.coronawarn.verification.model.HashedGuid; 28 | import app.coronawarn.verification.model.RegistrationToken; 29 | import app.coronawarn.verification.repository.VerificationAppSessionRepository; 30 | import app.coronawarn.verification.service.TestResultServerService; 31 | import lombok.extern.slf4j.Slf4j; 32 | import org.junit.jupiter.api.Test; 33 | import org.junit.jupiter.api.extension.ExtendWith; 34 | import org.springframework.beans.factory.annotation.Autowired; 35 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 36 | import org.springframework.boot.test.context.SpringBootTest; 37 | import org.springframework.boot.test.mock.mockito.MockBean; 38 | import org.springframework.http.MediaType; 39 | import org.springframework.test.context.ActiveProfiles; 40 | import org.springframework.test.context.ContextConfiguration; 41 | import org.springframework.test.context.junit.jupiter.SpringExtension; 42 | import org.springframework.test.web.servlet.MockMvc; 43 | 44 | /** 45 | * This is the test class for the verification application. 46 | */ 47 | @Slf4j 48 | @ExtendWith(SpringExtension.class) 49 | @SpringBootTest(properties = "disable-dob-hash-check-for-external-test-result=true") 50 | @ContextConfiguration(classes = VerificationApplication.class) 51 | @AutoConfigureMockMvc 52 | @ActiveProfiles({"external","local"}) 53 | public class VerificationApplicationExternalTestWorkaround { 54 | 55 | private static final String TOKEN_PADDING = "1"; 56 | @Autowired 57 | private MockMvc mockMvc; 58 | @MockBean 59 | private TestResultServerService testResultServerService; 60 | @Autowired 61 | private VerificationAppSessionRepository appSessionrepository; 62 | 63 | @Test 64 | public void callGetTestStateWithDobRegistrationTokenAndTrsRespondsWithDifferentResults() throws Exception { 65 | TestUtils.prepareAppSessionTestDataDob(appSessionrepository); 66 | 67 | given(this.testResultServerService.result(new HashedGuid(TestUtils.TEST_GUI_HASH))).willReturn(TestUtils.TEST_LAB_POSITIVE_RESULT); 68 | given(this.testResultServerService.result(new HashedGuid(TestUtils.TEST_GUI_HASH_DOB))).willReturn(TestUtils.TEST_LAB_NEGATIVE_RESULT); 69 | 70 | mockMvc.perform(post(TestUtils.PREFIX_API_VERSION + "/testresult").contentType(MediaType.APPLICATION_JSON) 71 | .secure(true) 72 | .content(TestUtils.getAsJsonFormat(new RegistrationToken(TestUtils.TEST_REG_TOK, TOKEN_PADDING)))) 73 | .andExpect(status().isOk()); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/app/coronawarn/verification/service/EntitiesCleanupServiceTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import app.coronawarn.verification.VerificationApplication; 24 | import app.coronawarn.verification.domain.VerificationAppSession; 25 | import app.coronawarn.verification.domain.VerificationTan; 26 | import app.coronawarn.verification.model.AppSessionSourceOfTrust; 27 | import app.coronawarn.verification.model.TanSourceOfTrust; 28 | import app.coronawarn.verification.model.TanType; 29 | import app.coronawarn.verification.repository.VerificationAppSessionRepository; 30 | import app.coronawarn.verification.repository.VerificationTanRepository; 31 | import java.time.LocalDateTime; 32 | import java.time.Period; 33 | import java.util.Optional; 34 | import java.util.concurrent.TimeUnit; 35 | import org.junit.jupiter.api.Assertions; 36 | import org.junit.jupiter.api.BeforeEach; 37 | import org.junit.jupiter.api.Test; 38 | import org.junit.jupiter.api.extension.ExtendWith; 39 | import org.springframework.beans.factory.annotation.Autowired; 40 | import org.springframework.boot.test.context.SpringBootTest; 41 | import org.springframework.test.context.ActiveProfiles; 42 | import org.springframework.test.context.ContextConfiguration; 43 | import org.springframework.test.context.junit.jupiter.SpringExtension; 44 | 45 | @ExtendWith(SpringExtension.class) 46 | @ActiveProfiles("local") 47 | @SpringBootTest( 48 | properties = { 49 | "entities.cleanup.cron=* * * * * *" 50 | } 51 | ) 52 | @ContextConfiguration(classes = VerificationApplication.class) 53 | public class EntitiesCleanupServiceTest { 54 | 55 | public static final String TEST_GUI_HASH = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; 56 | public static final String TEST_REG_TOK_HASH = "c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646"; 57 | public static final String TEST_HASHED_TAN = "16154ea91c2c59d6ef9d0e7f902a59283b1e7ff9111570d20139a4e6b1832876"; 58 | 59 | @Autowired 60 | private VerificationAppSessionRepository appSessionRepository; 61 | 62 | @Autowired 63 | private VerificationTanRepository tanRepository; 64 | 65 | @BeforeEach 66 | public void before() { 67 | appSessionRepository.deleteAll(); 68 | tanRepository.deleteAll(); 69 | } 70 | 71 | @Test 72 | public void cleanupDatabase() throws InterruptedException { 73 | LocalDateTime testCreationTime = LocalDateTime.now().minus(Period.ofDays(21)); 74 | // create repo 1 75 | VerificationAppSession session = appSessionRepository.save(getAppSessionTestData(testCreationTime)); 76 | Assertions.assertNotNull(session); 77 | Assertions.assertEquals(TEST_GUI_HASH, session.getHashedGuid()); 78 | // create repo 2 79 | VerificationTan tan = tanRepository.save(getVerificationTANTestData(testCreationTime)); 80 | Assertions.assertNotNull(tan); 81 | Assertions.assertEquals(TEST_HASHED_TAN, tan.getTanHash()); 82 | // find in repos 83 | Optional findSession = appSessionRepository.findByRegistrationTokenHash(TEST_REG_TOK_HASH); 84 | Assertions.assertTrue(findSession.isPresent()); 85 | Assertions.assertEquals(TEST_GUI_HASH, findSession.get().getHashedGuid()); 86 | 87 | Assertions.assertEquals(testCreationTime.withNano(5), findSession.get().getCreatedAt().withNano(5)); 88 | Optional findTan = tanRepository.findByTanHash(TEST_HASHED_TAN); 89 | Assertions.assertTrue(findTan.isPresent()); 90 | Assertions.assertEquals(TEST_HASHED_TAN, findTan.get().getTanHash()); 91 | Assertions.assertEquals(testCreationTime.withNano(5), findTan.get().getCreatedAt().withNano(5)); 92 | // wait 93 | TimeUnit.SECONDS.sleep(1); 94 | // find and check both repos clean up 95 | findSession = appSessionRepository.findByRegistrationTokenHash(TEST_REG_TOK_HASH); 96 | Assertions.assertFalse(findSession.isPresent()); 97 | findTan = tanRepository.findByTanHash(TEST_HASHED_TAN); 98 | Assertions.assertFalse(findTan.isPresent()); 99 | } 100 | 101 | private VerificationAppSession getAppSessionTestData(LocalDateTime testCreationTime) { 102 | VerificationAppSession cv = new VerificationAppSession(); 103 | cv.setHashedGuid(TEST_GUI_HASH); 104 | cv.setCreatedAt(testCreationTime); 105 | cv.setUpdatedAt(LocalDateTime.now()); 106 | cv.setTanCounter(0); 107 | cv.setSourceOfTrust(AppSessionSourceOfTrust.HASHED_GUID); 108 | cv.setRegistrationTokenHash(TEST_REG_TOK_HASH); 109 | return cv; 110 | } 111 | 112 | private VerificationTan getVerificationTANTestData(LocalDateTime testCreationTime) { 113 | VerificationTan cvtan = new VerificationTan(); 114 | cvtan.setCreatedAt(testCreationTime); 115 | cvtan.setUpdatedAt(LocalDateTime.now()); 116 | cvtan.setRedeemed(false); 117 | cvtan.setSourceOfTrust(TanSourceOfTrust.CONNECTED_LAB); 118 | cvtan.setTanHash(TEST_HASHED_TAN); 119 | cvtan.setType(TanType.TAN); 120 | cvtan.setValidFrom(LocalDateTime.now().minusDays(5)); 121 | cvtan.setValidUntil(LocalDateTime.now().plusDays(7)); 122 | return cvtan; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/app/coronawarn/verification/service/HashingServiceTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | import org.junit.jupiter.api.Test; 28 | 29 | public class HashingServiceTest { 30 | 31 | HashingService hashingService = new HashingService(); 32 | 33 | @Test 34 | public void validSha256Hash() { 35 | assertTrue(hashingService.isHashValid("523463041ef9ffa2950d8450feb34c88bc8692c40c9cf3c99dcdf75e270229e2")); 36 | assertTrue(hashingService.isHashValid("0000000000000000000000000000000000000000000000000000000000000000")); 37 | assertTrue(hashingService.isHashValid("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); 38 | } 39 | 40 | @Test 41 | public void invalidSha256Hash() { 42 | assertFalse(hashingService.isHashValid("x23463041ef9ffa2950d8z50feb34c88bc8692c40c9cf3c99dcdf75e270229e2")); 43 | assertFalse(hashingService.isHashValid("523463041ef9ffa2950d8z50feb34c88bc8692c40c9cf3c99dcdf75e270229e2")); 44 | assertFalse(hashingService.isHashValid("0")); 45 | assertFalse(hashingService.isHashValid("0000000000000000000000000000000000000000000000000000000000000000f")); 46 | assertFalse(hashingService.isHashValid("0000000000000000000000000000000000000000000000000000000000000000\n")); 47 | } 48 | 49 | @Test 50 | public void getCheckDigit() { 51 | assertThat(hashingService.getCheckDigit("FE9A5MAK6").equals("C")).isTrue(); 52 | assertThat(hashingService.getCheckDigit("WPHSATMHD").equals("4")).isTrue(); 53 | assertThat(hashingService.getCheckDigit("9N4UTTACE").equals("6")).isTrue(); 54 | assertThat(hashingService.getCheckDigit("S3HHJJYJD").equals("3")).isTrue(); 55 | assertThat(hashingService.getCheckDigit("W3M75DUD7").equals("C")).isTrue(); 56 | assertThat(hashingService.getCheckDigit("BBA3M8UVU").equals("C")).isTrue(); 57 | assertThat(hashingService.getCheckDigit("MNSHDZAEJ").equals("2")).isTrue(); 58 | assertThat(hashingService.getCheckDigit("WS732AR8Q").equals("B")).isTrue(); 59 | // special cases 60 | assertThat(hashingService.getCheckDigit("FE9A5MAK9").equals("H")).isTrue(); 61 | assertThat(hashingService.getCheckDigit("FE9A5MAKW").equals("G")).isTrue(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/app/coronawarn/verification/service/TestResultServerServiceTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---license-start 3 | * Corona-Warn-App / cwa-verification 4 | * --- 5 | * Copyright (C) 2020 - 2022 T-Systems International GmbH and all other contributors 6 | * --- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ---license-end 19 | */ 20 | 21 | package app.coronawarn.verification.service; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | import app.coronawarn.verification.client.TestResultServerClient; 26 | import app.coronawarn.verification.model.HashedGuid; 27 | import app.coronawarn.verification.model.TestResult; 28 | import org.junit.jupiter.api.BeforeEach; 29 | import org.junit.jupiter.api.Test; 30 | import org.springframework.test.context.ActiveProfiles; 31 | 32 | @ActiveProfiles("local") 33 | public class TestResultServerServiceTest { 34 | 35 | public static final String TEST_GUI_HASH_1 = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; 36 | public static final String TEST_GUI_HASH_2 = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13c"; 37 | private static final String TEST_RESULT_PADDING = ""; 38 | public static final TestResult TEST_LAB_POSITIVE_RESULT = new TestResult(2, 0, null, null); 39 | public static final TestResult TEST_LAB_REDEEMED_RESULT = new TestResult(4, 0, null, null); 40 | private TestResultServerService testResultServerService; 41 | 42 | @BeforeEach 43 | public void setUp() { 44 | testResultServerService = new TestResultServerService(new TestResultServerClientMock()); 45 | } 46 | 47 | /** 48 | * Test result method by positive status. 49 | */ 50 | @Test 51 | public void resultPositive() { 52 | TestResult testResult = testResultServerService.result(new HashedGuid(TEST_GUI_HASH_1)); 53 | assertThat(testResult).isEqualTo(TEST_LAB_POSITIVE_RESULT); 54 | } 55 | 56 | /** 57 | * Test result method by redeemed status. 58 | */ 59 | @Test 60 | public void resultRedeemed() { 61 | TestResult testResult = testResultServerService.result(new HashedGuid(TEST_GUI_HASH_2)); 62 | assertThat(testResult).isEqualTo(TEST_LAB_REDEEMED_RESULT); 63 | } 64 | 65 | public static class TestResultServerClientMock implements TestResultServerClient { 66 | @Override 67 | public TestResult result(HashedGuid guid) { 68 | if (guid.getId().equals(TEST_GUI_HASH_1)) { 69 | return new TestResult(2, 0, null, null); 70 | } 71 | return new TestResult(4, 0, null, null); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /trusted.key.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corona-warn-app/cwa-verification-server/bbc28be761c89bdda30f761f79f3a190e4c3a7c6/trusted.key.gpg --------------------------------------------------------------------------------