Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Commit

Permalink
Add archive feature (#28)
Browse files Browse the repository at this point in the history
* Add db_encryption

* Add db_encryption

* Add db_encryption

* Add db_encryption

* Add unittest for db_encryption

* - QuickTestArchive Feature started:
  - controller added
  - Entity added
  - Repository added
  - Service added
- bugfix:
  - removed some utests (annotation missing now, utests broken by ???)
 - state: application context destroyed by fetching branch - not runnable version now

* Reformate Code

* - fix: uri wrong
- removed extends entity
- change blob to clob
- revert delete utests
- state: runnig version now

* - removed tests without assert

* - pdf upload check content type added
- pdf encryption added
- seperated archive entity to own chnage id in liquibase changelog

* - current version

* Add statistictable

* Add statisticstable

* - change statistics id to long
- add findByPocAndDate to statistics repo
- rollback scope for transaction in archive optimized
- finished statistics functions

* - security options enabled

* - log outputs added
- log outputs optimized
- refactoring utests
- utests added

* - Fix statistics endpoint
- Fix update testresult endpoint
- reformat archivetable

* - Insert Test for Utilities
- Fix Utilities

* Added birthday column to QuickTest

* - refactoring move create archive to testResult
- birthday fixed
- pdf creator built in - state: proving

* - Change KeycloakRealm to load by Request

* - Change KeycloakRealm to load by Request

* - Fix getPdf

* - Fix KeycloakDeploymentBuilder => default if no auth

* - change pdf endpoint
- change statistics repository

* - Fix check if hashedGuid exist in archive

* Added endpoint for querying archive, moved testBrand to testResult endpoint

* - hashedGuid added to response (GUI needs for request pdf)

* - Fix pdf-generation

* - debug lines removed

* Fixed testResult endpoint

Co-authored-by: Matthias Baude <[email protected]>
Co-authored-by: Eduard Gerling <[email protected]>
  • Loading branch information
3 people authored Apr 16, 2021
1 parent cd0798f commit d53bab7
Show file tree
Hide file tree
Showing 38 changed files with 1,503 additions and 119 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<keycloak.version>10.0.1</keycloak.version>
<springdoc.version>1.4.2</springdoc.version>
<liquibase.version>4.3.2</liquibase.version>
<pdfbox.version>2.0.23</pdfbox.version>
<!-- plugins -->
<plugin.checkstyle.version>3.1.2</plugin.checkstyle.version>
<license.projectName>Corona-Warn-App / cwa-quick-test-backend</license.projectName>
Expand Down Expand Up @@ -136,6 +137,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${pdfbox.version}</version>
</dependency>

<!-- DB drivers -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package app.coronawarn.quicktest.config;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import lombok.SneakyThrows;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuicktestKeycloakSpringBootConfigResolver extends KeycloakSpringBootConfigResolver {
private final AdapterConfig adapterConfig;

public QuicktestKeycloakSpringBootConfigResolver(KeycloakSpringBootProperties properties) {
this.adapterConfig = properties;
}

@SneakyThrows
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
AdapterConfig tenantConfig = this.adapterConfig;
JSONObject jwtBodyAsJson = null;
String realm = null;
if (
request.getHeader("Authorization") != null
&& request.getHeader("Authorization").split("Bearer ").length > 1
&& request.getHeader("Authorization").split("Bearer ")[1].split("\\.").length > 1
) {
//Remove Bearer and split in three parts => take the second with the body information
String jwtBody = request.getHeader("Authorization").split("Bearer ")[1].split("\\.")[1];
//Decode and convert in Json
jwtBodyAsJson = new JSONObject(new String(Base64.getDecoder().decode(jwtBody),
StandardCharsets.UTF_8));
}
if (
jwtBodyAsJson != null
&& jwtBodyAsJson.get("iss") != null
&& jwtBodyAsJson.get("iss").toString().split("/").length > 0) {
//get issuerUri from body and split url by /
String[] issuerUriElements = jwtBodyAsJson.get("iss").toString().split("/");
//get last element from issuerUriElements => realm name
realm = issuerUriElements[issuerUriElements.length - 1];
}
if (realm != null) {
tenantConfig.setRealm(realm);
}
return KeycloakDeploymentBuilder.build(tenantConfig);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -57,8 +58,8 @@ protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
}

@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
public KeycloakSpringBootConfigResolver keycloakConfigResolver(KeycloakSpringBootProperties properties) {
return new QuicktestKeycloakSpringBootConfigResolver(properties);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ public class KeyCloakConfigController {
@GetMapping(value = "keycloak.json", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<KeyCloakConfigFile> getKeyCloakConfig() {
return ResponseEntity.ok(
new KeyCloakConfigFile(keycloakConfig.getRealm(), keycloakConfig.getAuthServerUrl()));
new KeyCloakConfigFile(keycloakConfig.getAuthServerUrl()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*-
* ---license-start
* Corona-Warn-App / cwa-quick-test-backend
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*/

package app.coronawarn.quicktest.controller;

import static app.coronawarn.quicktest.config.SecurityConfig.ROLE_LAB;

import app.coronawarn.quicktest.domain.QuickTestArchive;
import app.coronawarn.quicktest.model.QuickTestArchiveListResponse;
import app.coronawarn.quicktest.model.QuickTestArchiveResponse;
import app.coronawarn.quicktest.service.QuickTestArchiveService;
import app.coronawarn.quicktest.service.QuickTestServiceException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import java.time.LocalDateTime;
import java.util.List;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping(value = "/api/quicktestarchive")
@RequiredArgsConstructor
@Validated
public class QuickTestArchiveController {

private final QuickTestArchiveService quickTestArchiveService;
private final ModelMapper modelMapper;

/**
* Endpoint for receiving pdf.
*
* @param hashedGuid containing the full hashed guid.
* @return PDF
*/
@Operation(
summary = "Response quicktest as PDF",
description = "PDF stored in DB will be responsed for download if found."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "PDF found"),
@ApiResponse(responseCode = "404", description = "Quicktest not found"),
@ApiResponse(responseCode = "500", description = "Inserting failed because of internal error.")})
@RequestMapping(path = "/{hashedGuid}/pdf", method = RequestMethod.GET, produces = MediaType.APPLICATION_PDF_VALUE)
@Secured(ROLE_LAB)
public ResponseEntity<byte[]> createQuickTestArchive(
@PathVariable String hashedGuid) {
try {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""
+ "Schnelltest_" + hashedGuid + "\"")
.body(quickTestArchiveService.getPdf(hashedGuid));
} catch (QuickTestServiceException e) {
if (e.getReason() == QuickTestServiceException.Reason.NOT_FOUND) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
} catch (Exception e) {
log.error("Couldn't prepare stored pdf for download. Message: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* Endpoint for getting quicktests in archive table by query parameters.
*
* @return QuickTestArchiveListResponse with all found archives
*/
@Operation(
summary = "Find quicktests in archive",
description = "Returns all found quicktests in archive for search parameters"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successful"),
@ApiResponse(responseCode = "500", description = "Query failed because of an internal server error")
})
@GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
@Secured(ROLE_LAB)
public ResponseEntity<QuickTestArchiveListResponse> findArchivesByTestResultAndUpdatedAtBetween(
@RequestParam @Min(5) @Max(8) Short testResult,
@RequestParam("dateFrom") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime localDateFrom,
@RequestParam("dateTo") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime localDateTo) {
try {
List<QuickTestArchive> archives = quickTestArchiveService.findByTestResultAndUpdatedAtBetween(
testResult, localDateFrom, localDateTo);
TypeToken<List<QuickTestArchiveResponse>> typeToken = new TypeToken<List<QuickTestArchiveResponse>>() {
};
List<QuickTestArchiveResponse> quickTestArchiveResponses = modelMapper.map(
archives,
typeToken.getType()
);
QuickTestArchiveListResponse response = new QuickTestArchiveListResponse();
response.setQuickTestArchives(quickTestArchiveResponses);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ public ResponseEntity<Void> updateQuickTestStatus(
try {
quickTestService.updateQuickTest(
utilities.getIdsFromToken(),
shortHash, quickTestUpdateRequest.getResult());
shortHash,
quickTestUpdateRequest.getResult(),
quickTestUpdateRequest.getTestBrandId(),
quickTestUpdateRequest.getTestBrandName()
);
} catch (QuickTestServiceException e) {
if (e.getReason() == QuickTestServiceException.Reason.UPDATE_NOT_FOUND) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*-
* ---license-start
* Corona-Warn-App / cwa-quick-test-backend
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*/

package app.coronawarn.quicktest.controller;

import static app.coronawarn.quicktest.config.SecurityConfig.ROLE_COUNTER;

import app.coronawarn.quicktest.model.QuickTestStatisticsResponse;
import app.coronawarn.quicktest.service.QuickTestServiceException;
import app.coronawarn.quicktest.service.QuickTestStatisticsService;
import app.coronawarn.quicktest.utils.Utilities;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping(value = "/api/quickteststatistics")
@RequiredArgsConstructor
public class QuickTestStatisticsController {

private final QuickTestStatisticsService quickTestStatisticsService;

private final ModelMapper modelMapper;

private final Utilities utilities;


//TODO check role
/**
* Endpoint for get statistic for QuickTest.
*
* @return QuickTestStatisticsResponse with total and positive count
*/
@Operation(
summary = "Get quicktest statistics",
description = "Returns the statistics for total and positive counts for today"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Get statistic data"),
@ApiResponse(responseCode = "500", description = "Inserting failed because of internal error.")})
@GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
@Secured(ROLE_COUNTER)
public ResponseEntity<QuickTestStatisticsResponse> getQuicktestStatistics() {
try {
QuickTestStatisticsResponse quickTestStatisticsResponse = modelMapper.map(
quickTestStatisticsService.getStatistics(utilities.getIdsFromToken()),
QuickTestStatisticsResponse.class);

return ResponseEntity.ok(quickTestStatisticsResponse);
} catch (QuickTestServiceException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package app.coronawarn.quicktest.dbencryption;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.persistence.AttributeConverter;
import javax.persistence.PersistenceException;

public class DbEncryptionByteArrayConverter implements AttributeConverter<byte[], String> {

@Override
public String convertToDatabaseColumn(byte[] s) {
try {
return DbEncryptionService.getInstance().encryptByteArray(s);
} catch (InvalidAlgorithmParameterException | InvalidKeyException
| BadPaddingException | IllegalBlockSizeException e) {
throw new PersistenceException(e);
}
}

@Override
public byte[] convertToEntityAttribute(String s) {
try {
return DbEncryptionService.getInstance().decryptByteArray(s);
} catch (InvalidAlgorithmParameterException | InvalidKeyException
| BadPaddingException | IllegalBlockSizeException e) {
throw new PersistenceException(e);
}
}

}
Loading

0 comments on commit d53bab7

Please sign in to comment.