From 48930773b14ed6b9560f53c6c42aaed0e9c2c5c6 Mon Sep 17 00:00:00 2001 From: Hong Mu <100763593+hhmu@users.noreply.github.com> Date: Thu, 24 Mar 2022 09:04:24 +0100 Subject: [PATCH] feat: dsc delta download extension (#169) * added Delta&Pagination in SignerInformationService * added Delta&Pagination in TrustedPartyService * added Delta&Pagination in TrustListService * added Delta&Pagination in TrustListController * fixed sonar warning * fixed sonar warning and updated type of ifModifiedSince to String * fixed sonar warning and updated type of ifModifiedSince to String * flag SignerInformationEntity for deletion (#166) * flag SignerInformationEntity for deletion * add cleanup scheduler for signerInformation * Update pom.xml * keep thumbprint for identification * fix after merge * add deleted to modified-since response Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * unit test * fixed unit test * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * Update src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> * fix IfModifiedSince DateTimeFormat advice and unit tests Co-authored-by: Hong Mu Co-authored-by: bergmann-dierk <84856774+bergmann-dierk@users.noreply.github.com> Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> Co-authored-by: Dierk Bergmann Co-authored-by: Andreas Scheibal --- pom.xml | 2 +- .../gateway/config/DgcConfigProperties.java | 8 + .../entity/SignerInformationEntity.java | 5 +- .../SignerInformationRepository.java | 54 +++- .../repository/TrustedPartyRepository.java | 23 ++ .../controller/TrustListController.java | 140 ++++++++- .../SignerInformationCleanUpService.java | 56 ++++ .../service/SignerInformationService.java | 118 ++++++-- .../dgc/gateway/service/TrustListService.java | 124 +++++++- .../gateway/service/TrustedPartyService.java | 114 ++++++++ .../ec/dgc/gateway/utils/ListUtils.java | 55 ++++ src/main/resources/application.yml | 2 + src/main/resources/db/changelog.xml | 1 + .../alter-signer-information-for-deletion.xml | 15 + .../CertificateMigrationControllerTest.java | 2 +- .../controller/TrustListIntegrationTest.java | 265 +++++++++++++++--- .../SignerInformationCleanUpServiceTest.java | 85 ++++++ .../service/SignerInformationServiceTest.java | 156 ++++++++++- .../gateway/service/TrustListServiceTest.java | 71 +++-- .../service/TrustedPartyServiceTest.java | 119 +++++++- .../testdata/SignerInformationTestHelper.java | 58 ++++ .../testdata/TrustedPartyTestHelper.java | 45 ++- 22 files changed, 1403 insertions(+), 115 deletions(-) create mode 100644 src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpService.java create mode 100644 src/main/java/eu/europa/ec/dgc/gateway/utils/ListUtils.java create mode 100644 src/main/resources/db/changelog/alter-signer-information-for-deletion.xml create mode 100644 src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpServiceTest.java create mode 100644 src/test/java/eu/europa/ec/dgc/gateway/testdata/SignerInformationTestHelper.java diff --git a/pom.xml b/pom.xml index d14539e6..d5cfb251 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 6.5.3 5.6.2 1.18.22 - 4.8.0 + 4.9.0 1.6.6 1.4.2.Final 4.3.1 diff --git a/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java b/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java index d836ce2d..898d0ea1 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/config/DgcConfigProperties.java @@ -39,6 +39,8 @@ public class DgcConfigProperties { private Revocation revocation = new Revocation(); + private SignerInformation signerInformation = new SignerInformation(); + @Getter @Setter public static class JrcConfig { @@ -85,4 +87,10 @@ public static class HeaderFields { public static class Revocation { private int deleteThreshold = 14; } + + @Getter + @Setter + public static class SignerInformation { + private int deleteThreshold = 14; + } } diff --git a/src/main/java/eu/europa/ec/dgc/gateway/entity/SignerInformationEntity.java b/src/main/java/eu/europa/ec/dgc/gateway/entity/SignerInformationEntity.java index 864c6978..825e48f7 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/entity/SignerInformationEntity.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/entity/SignerInformationEntity.java @@ -53,6 +53,9 @@ public class SignerInformationEntity { @Column(name = "created_at", nullable = false) private ZonedDateTime createdAt = ZonedDateTime.now(); + @Column(name = "deleted_at") + private ZonedDateTime deletedAt; + /** * ISO 3166 Alpha-2 Country Code * (plus code "EU" for administrative European Union entries). @@ -75,7 +78,7 @@ public class SignerInformationEntity { /** * Signature of the TrustAnchor. */ - @Column(name = "signature", nullable = false, length = 6000) + @Column(name = "signature", length = 6000) String signature; /** diff --git a/src/main/java/eu/europa/ec/dgc/gateway/repository/SignerInformationRepository.java b/src/main/java/eu/europa/ec/dgc/gateway/repository/SignerInformationRepository.java index f8b97916..d1f1536e 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/repository/SignerInformationRepository.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/repository/SignerInformationRepository.java @@ -21,23 +21,75 @@ package eu.europa.ec.dgc.gateway.repository; import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; +import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import javax.transaction.Transactional; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface SignerInformationRepository extends JpaRepository { + String SELECT_SINCE = + "SELECT s FROM SignerInformationEntity s WHERE s.createdAt >= :since OR s.deletedAt >= :since"; + String SELECT_BY_TYPE_SINCE = + "SELECT s FROM SignerInformationEntity s WHERE s.certificateType = :certType AND (s.createdAt >= :since " + + " OR s.deletedAt >= :since)"; + String SELECT_BY_TYPE_AND_COUNTRY_SINCE = + "SELECT s FROM SignerInformationEntity s" + + " WHERE s.certificateType = :certType AND s.country = :country AND (s.createdAt >= :since" + + " OR s.deletedAt >= :since)"; + Optional getFirstByThumbprint(String thumbprint); Optional getFirstByThumbprintStartsWith(String thumbprintStart); @Transactional - void deleteByThumbprint(String thumbprint); + @Modifying + @Query("DELETE FROM SignerInformationEntity s WHERE s.deletedAt < :threshold") + int deleteDeletedSignerInformationOlderThan(@Param("threshold") ZonedDateTime threshold); + + List getByCertificateType(SignerInformationEntity.CertificateType type, + Pageable pageable); List getByCertificateType(SignerInformationEntity.CertificateType type); + List getByCertificateTypeAndCountry( + SignerInformationEntity.CertificateType type, String countryCode, + Pageable pageable); + List getByCertificateTypeAndCountry( SignerInformationEntity.CertificateType type, String countryCode); + @Query(SELECT_SINCE) + List getIsSince(@Param("since")ZonedDateTime since); + + @Query(SELECT_SINCE) + List getIsSince(@Param("since")ZonedDateTime since, Pageable pageable); + + @Query(SELECT_BY_TYPE_SINCE) + List getByCertificateTypeIsSince( + @Param("certType")SignerInformationEntity.CertificateType type, + @Param("since")ZonedDateTime since); + + @Query(SELECT_BY_TYPE_SINCE) + List getByCertificateTypeIsSince( + @Param("certType")SignerInformationEntity.CertificateType type, + @Param("since")ZonedDateTime since, Pageable pageable); + + @Query(SELECT_BY_TYPE_AND_COUNTRY_SINCE) + List getByCertificateTypeAndCountryIsSince( + @Param("certType")SignerInformationEntity.CertificateType type, + @Param("country")String countryCode, + @Param("since")ZonedDateTime since); + + @Query(SELECT_BY_TYPE_AND_COUNTRY_SINCE) + List getByCertificateTypeAndCountryIsSince( + @Param("certType")SignerInformationEntity.CertificateType type, + @Param("country")String countryCode, + @Param("since")ZonedDateTime since, Pageable pageable); + } diff --git a/src/main/java/eu/europa/ec/dgc/gateway/repository/TrustedPartyRepository.java b/src/main/java/eu/europa/ec/dgc/gateway/repository/TrustedPartyRepository.java index 101f1358..18e4f1c0 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/repository/TrustedPartyRepository.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/repository/TrustedPartyRepository.java @@ -21,13 +21,22 @@ package eu.europa.ec.dgc.gateway.repository; import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; +import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface TrustedPartyRepository extends JpaRepository { + String SELECT_SINCE = "SELECT t FROM TrustedPartyEntity t WHERE t.createdAt >= :since"; + String SELECT_BY_TYPE_SINCE = + "SELECT t FROM TrustedPartyEntity t WHERE t.certificateType = :certType AND t.createdAt >= :since"; + String SELECT_BY_TYPE_AND_COUNTRY_SINCE = + "SELECT t FROM TrustedPartyEntity t" + + " WHERE t.certificateType = :certType AND t.country = :country AND t.createdAt >= :since"; + List getByCountryAndCertificateType(String country, TrustedPartyEntity.CertificateType type); List getByCertificateType(TrustedPartyEntity.CertificateType type); @@ -41,4 +50,18 @@ Optional getFirstByThumbprintAndCertificateType( @Query("SELECT DISTINCT t.country FROM TrustedPartyEntity t") List getCountryCodeList(); + @Query(SELECT_SINCE) + List getIsSince(@Param("since") ZonedDateTime since); + + @Query(SELECT_BY_TYPE_SINCE) + List getByCertificateTypeIsSince( + @Param("certType")TrustedPartyEntity.CertificateType type, + @Param("since")ZonedDateTime since); + + @Query(SELECT_BY_TYPE_AND_COUNTRY_SINCE) + List getByCountryAndCertificateTypeIsSince( + @Param("country")String countryCode, + @Param("certType")TrustedPartyEntity.CertificateType type, + @Param("since")ZonedDateTime since); + } diff --git a/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java b/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java index 227b5925..9e4c594e 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListController.java @@ -42,6 +42,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.time.ZonedDateTime; import java.util.List; import java.util.Locale; import javax.validation.Valid; @@ -49,12 +50,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.RequestAttribute; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -91,13 +95,43 @@ public class TrustListController { }, summary = "Returns the full list of trusted certificates.", tags = {"Trust Lists"}, + parameters = { + @Parameter( + in = ParameterIn.HEADER, + name = HttpHeaders.IF_MODIFIED_SINCE, + description = "Defines if only updated certificates since the given date should be returned.", + required = false, + schema = @Schema(implementation = String.class), + example = "Wed, 21 Oct 2015 07:28:00 GMT"), + @Parameter( + in = ParameterIn.QUERY, + name = "page", + description = "Page index, must NOT be negative.", + required = false, + schema = @Schema(implementation = Integer.class), + example = "0"), + @Parameter( + in = ParameterIn.QUERY, + description = "Number of certificates in a page to be returned, must be greater than 0.", + required = false, + schema = @Schema(implementation = Integer.class), + example = "10") + }, responses = { @ApiResponse( responseCode = "200", - description = "Returns the full list of trusted parties.", + description = "Returns the full list of trusted parties. Optional the download can be paginated" + + " and a delta download will be enabled by the header parameter 'If-Modified-Since'.", content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = TrustListDto.class)))), + @ApiResponse( + responseCode = "400", + description = "Bad request. Invalid date in HTTP header 'If-Modified-Since'.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemReportDto.class) + )), @ApiResponse( responseCode = "401", description = "Unauthorized. No Access to the system. (Client Certificate not present or whitelisted)", @@ -107,10 +141,20 @@ public class TrustListController { )) }) public ResponseEntity> downloadTrustList( + @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime ifModifiedSince, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "pagesize", required = false) Integer size, @RequestAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY) String downloaderCountryCode ) { - List trustList = trustListMapper.trustListToTrustListDto(trustListService.getTrustList()); - + List trustList; + if (isPaginationRequired(page,size)) { + trustList = trustListMapper.trustListToTrustListDto( + trustListService.getTrustList(ifModifiedSince, page, size)); + } else { + trustList = trustListMapper.trustListToTrustListDto( + trustListService.getTrustList(ifModifiedSince, null, null)); + } DgcMdc.put(MDC_PROP_DOWNLOAD_KEYS_COUNT, trustList.size()); DgcMdc.put(MDC_PROP_DOWNLOAD_KEYS_COUNTRY, downloaderCountryCode); @@ -137,18 +181,41 @@ public ResponseEntity> downloadTrustList( name = "type", description = "Certificate Type to filter for", required = true, - schema = @Schema(implementation = CertificateTypeDto.class)) + schema = @Schema(implementation = CertificateTypeDto.class)), + @Parameter( + in = ParameterIn.HEADER, + name = HttpHeaders.IF_MODIFIED_SINCE, + description = "Defines if only updated certificates since the given date should be returned.", + required = false, + schema = @Schema(implementation = String.class), + example = "Wed, 21 Oct 2015 07:28:00 GMT"), + @Parameter( + in = ParameterIn.QUERY, + name = "page", + description = "Page index, must NOT be negative.", + required = false, + schema = @Schema(implementation = Integer.class), + example = "0"), + @Parameter( + in = ParameterIn.QUERY, + name = "pagesize", + description = "Number of certificates in a page to be returned, must be greater than 0.", + required = false, + schema = @Schema(implementation = Integer.class), + example = "10") }, responses = { @ApiResponse( responseCode = "200", - description = "Returns a filtered list of trusted certificates.", + description = "Returns a filtered list of trusted certificates. Optional the download can be paginated" + + " and a delta download will be enabled by the header parameter 'If-Modified-Since'.", content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = TrustListDto.class)))), @ApiResponse( responseCode = "400", - description = "Bad request. Unknown Certificate Type.", + description = "Bad request. Unknown Certificate Type or invalid date in " + + "HTTP header 'If-Modified-Since'.", content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemReportDto.class) @@ -163,13 +230,22 @@ public ResponseEntity> downloadTrustList( }) public ResponseEntity> downloadTrustListFilteredByType( @Valid @PathVariable("type") CertificateTypeDto type, + @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime ifModifiedSince, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "pagesize", required = false) Integer size, @RequestAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY) String downloaderCountryCode ) { TrustListType mappedType = trustListMapper.certificateTypeDtoToTrustListType(type); - - List trustList = trustListMapper.trustListToTrustListDto( - trustListService.getTrustList(mappedType)); + List trustList; + if (isPaginationRequired(page,size)) { + trustList = trustListMapper.trustListToTrustListDto( + trustListService.getTrustList(mappedType, ifModifiedSince, page, size)); + } else { + trustList = trustListMapper.trustListToTrustListDto( + trustListService.getTrustList(mappedType, ifModifiedSince, null, null)); + } DgcMdc.put(MDC_PROP_DOWNLOAD_KEYS_COUNT, trustList.size()); DgcMdc.put(MDC_PROP_DOWNLOAD_KEYS_TYPE, type.name()); @@ -204,18 +280,41 @@ public ResponseEntity> downloadTrustListFilteredByType( name = "country", description = "2-Digit Country Code to filter for", example = "EU", - required = true) + required = true), + @Parameter( + in = ParameterIn.HEADER, + name = HttpHeaders.IF_MODIFIED_SINCE, + description = "Defines if only updated certificates since the given date should be returned.", + required = false, + schema = @Schema(implementation = String.class), + example = "Wed, 21 Oct 2015 07:28:00 GMT"), + @Parameter( + in = ParameterIn.QUERY, + name = "page", + description = "Page index, must NOT be negative.", + required = false, + schema = @Schema(implementation = Integer.class), + example = "0"), + @Parameter( + in = ParameterIn.QUERY, + name = "pagesize", + description = "Number of certificates in a page to be returned, must be greater than 0.", + required = false, + schema = @Schema(implementation = Integer.class), + example = "10") }, responses = { @ApiResponse( responseCode = "200", - description = "Returns a filtered list of trusted certificates.", + description = "Returns a filtered list of trusted certificates. Optional the download can be paginated" + + " and a delta download will be enabled by the header parameter 'If-Modified-Since'.", content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = TrustListDto.class)))), @ApiResponse( responseCode = "400", - description = "Bad request. Unknown Certificate Type or invalid country code.", + description = "Bad request. Unknown Certificate Type or invalid country code or " + + "invalid date in HTTP header 'If-Modified-Since'.", content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemReportDto.class) @@ -231,14 +330,24 @@ public ResponseEntity> downloadTrustListFilteredByType( public ResponseEntity> downloadTrustListFilteredByCountryAndType( @Valid @PathVariable("type") CertificateTypeDto type, @Valid @Size(max = 2, min = 2) @PathVariable("country") String countryCode, + @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime ifModifiedSince, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "pagesize", required = false) Integer size, @RequestAttribute(CertificateAuthenticationFilter.REQUEST_PROP_COUNTRY) String downloaderCountryCode ) { TrustListType mappedType = trustListMapper.certificateTypeDtoToTrustListType(type); countryCode = countryCode.toUpperCase(Locale.ROOT); - List trustList = trustListMapper.trustListToTrustListDto( - trustListService.getTrustList(mappedType, countryCode)); + List trustList; + if (isPaginationRequired(page,size)) { + trustList = trustListMapper.trustListToTrustListDto( + trustListService.getTrustList(mappedType, countryCode, ifModifiedSince, page, size)); + } else { + trustList = trustListMapper.trustListToTrustListDto( + trustListService.getTrustList(mappedType, countryCode, ifModifiedSince, null, null)); + } DgcMdc.put(MDC_PROP_DOWNLOAD_KEYS_COUNT, trustList.size()); DgcMdc.put(MDC_PROP_DOWNLOAD_KEYS_TYPE, type.name()); @@ -297,6 +406,9 @@ public ResponseEntity> getTrustedIssuersByCountry( return ResponseEntity.ok(trustedIssuerMapper.trustedIssuerEntityToTrustedIssuerDto( trustedIssuerService.getAllIssuers())); } + } + private boolean isPaginationRequired(Integer page, Integer size) { + return page != null && size != null && page >= 0 && size > 0; } } diff --git a/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpService.java b/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpService.java new file mode 100644 index 00000000..c7ade469 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpService.java @@ -0,0 +1,56 @@ +/*- + * ---license-start + * EU Digital Green Certificate Gateway Service / dgc-gateway + * --- + * Copyright (C) 2021 - 2022 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 eu.europa.ec.dgc.gateway.service; + +import eu.europa.ec.dgc.gateway.config.DgcConfigProperties; +import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository; +import java.time.ZonedDateTime; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class SignerInformationCleanUpService { + + private final SignerInformationRepository signerInformationRepository; + + private final DgcConfigProperties configProperties; + + /** + * Delete SignerInformationEntity which are flagged for deletion. + */ + @Scheduled(cron = "@daily") + @SchedulerLock(name = "signer_information_cleanup") + public void cleanup() { + log.info("Starting SignerInformation Cleanup Job."); + + int affectedRowsDeleted = signerInformationRepository.deleteDeletedSignerInformationOlderThan( + ZonedDateTime.now().minusDays(configProperties.getSignerInformation().getDeleteThreshold()) + ); + log.info("Deleted {} SignerInformation.", affectedRowsDeleted); + + log.info("Completed SignerInformation Cleanup Job."); + } +} diff --git a/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationService.java b/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationService.java index 3353a40b..62127c16 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationService.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/service/SignerInformationService.java @@ -28,6 +28,7 @@ import eu.europa.ec.dgc.utils.CertificateUtils; import java.io.IOException; import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; import java.util.Base64; import java.util.List; import java.util.Optional; @@ -43,6 +44,7 @@ import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.RuntimeOperatorException; import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @@ -91,6 +93,84 @@ public List getSignerInformation( return signerInformationRepository.getByCertificateTypeAndCountry(type, countryCode); } + /** + * Finds a list of SignerInformation. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param ifModifiedSince since timestamp for filtering SignerInformation. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return list of SignerInformation + */ + public List getSignerInformation(ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + if (ifModifiedSince != null && page != null && size != null) { + return signerInformationRepository.getIsSince(ifModifiedSince, PageRequest.of(page, size)); + } else if (ifModifiedSince != null) { + return signerInformationRepository.getIsSince(ifModifiedSince); + } else if (page != null && size != null) { + return signerInformationRepository.findAll(PageRequest.of(page, size)).toList(); + } else { + return getSignerInformation(); + } + } + + /** + * Finds a list of SignerInformation filtered by type. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param type type to filter for + * @param ifModifiedSince since timestamp for filtering SignerInformation. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of SignerInformation + */ + public List getSignerInformation(SignerInformationEntity.CertificateType type, + ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + if (ifModifiedSince != null && page != null && size != null) { + return signerInformationRepository.getByCertificateTypeIsSince(type, + ifModifiedSince, PageRequest.of(page, size)); + } else if (ifModifiedSince != null) { + return signerInformationRepository.getByCertificateTypeIsSince(type, ifModifiedSince); + } else if (page != null && size != null) { + return signerInformationRepository.getByCertificateType(type, + PageRequest.of(page, size)); + } else { + return signerInformationRepository.getByCertificateType(type); + } + } + + /** + * Finds a list of SignerInformation filtered by type and country. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param countryCode 2-digit country Code to filter for. + * @param type type to filter for + * @param ifModifiedSince since timestamp for filtering SignerInformation. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of SignerInformation + */ + public List getSignerInformation( + String countryCode, + SignerInformationEntity.CertificateType type, + ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + if (ifModifiedSince != null && page != null && size != null) { + return signerInformationRepository.getByCertificateTypeAndCountryIsSince(type, countryCode, + ifModifiedSince, PageRequest.of(page, size)); + } else if (ifModifiedSince != null) { + return signerInformationRepository.getByCertificateTypeAndCountryIsSince(type, countryCode, + ifModifiedSince); + } else if (page != null && size != null) { + return signerInformationRepository.getByCertificateTypeAndCountry(type, countryCode, + PageRequest.of(page, size)); + } else { + return signerInformationRepository.getByCertificateTypeAndCountry(type, countryCode); + } + } + /** * Adds a new Trusted Signer Certificate to TrustStore DB. * @@ -143,7 +223,7 @@ public SignerInformationEntity addSignerCertificate( /** * Update a CMS package. * - * @param id The entity to update + * @param id The entity to update * @param uploadedCertificate the certificate to add * @param signerCertificate the certificate which was used to sign the message * @param signature the detached signature of cms message @@ -152,11 +232,11 @@ public SignerInformationEntity addSignerCertificate( * a reason property with detailed information why the validation has failed. */ public SignerInformationEntity updateSignerCertificate( - Long id, - X509CertificateHolder uploadedCertificate, - X509CertificateHolder signerCertificate, - String signature, - String authenticatedCountryCode + Long id, + X509CertificateHolder uploadedCertificate, + X509CertificateHolder signerCertificate, + String signature, + String authenticatedCountryCode ) throws SignerCertCheckException { final SignerInformationEntity signerInformationEntity = signerInformationRepository.findById(id).orElseThrow( @@ -206,12 +286,14 @@ public void deleteSignerCertificate( contentCheckUploaderCertificate(signerCertificate, authenticatedCountryCode); contentCheckCountryOfOrigin(uploadedCertificate, authenticatedCountryCode); - contentCheckExists(uploadedCertificate); + SignerInformationEntity signerInformationEntity = contentCheckExists(uploadedCertificate); log.info("Revoking SignerInformation Entity"); - // All checks passed --> Delete from DB - signerInformationRepository.deleteByThumbprint(certificateUtils.getCertThumbprint(uploadedCertificate)); + // All checks passed --> Delete from DB, set fields to null + signerInformationEntity.setDeletedAt(ZonedDateTime.now()); + signerInformationEntity.setSignature(null); + signerInformationRepository.save(signerInformationEntity); DgcMdc.remove(MDC_PROP_UPLOAD_CERT_THUMBPRINT); } @@ -224,10 +306,10 @@ public void deleteSignerCertificate( */ public List getCmsPackage(String country) { return signerInformationRepository - .getByCertificateTypeAndCountry(SignerInformationEntity.CertificateType.DSC, country) - .stream() - .map(it -> new CmsPackageDto(it.getSignature(), it.getId(), CmsPackageDto.CmsPackageTypeDto.DSC)) - .collect(Collectors.toList()); + .getByCertificateTypeAndCountry(SignerInformationEntity.CertificateType.DSC, country) + .stream() + .map(it -> new CmsPackageDto(it.getSignature(), it.getId(), CmsPackageDto.CmsPackageTypeDto.DSC)) + .collect(Collectors.toList()); } private void contentCheckUploaderCertificate( @@ -323,16 +405,16 @@ private void contentCheckKidAlreadyExists(X509CertificateHolder uploadedCertific } } - private void contentCheckExists(X509CertificateHolder uploadedCertificate) throws SignerCertCheckException { + private SignerInformationEntity contentCheckExists(X509CertificateHolder uploadedCertificate) + throws SignerCertCheckException { String uploadedCertificateThumbprint = certificateUtils.getCertThumbprint(uploadedCertificate); Optional signerInformationEntity = signerInformationRepository.getFirstByThumbprint(uploadedCertificateThumbprint); - if (signerInformationEntity.isEmpty()) { - throw new SignerCertCheckException(SignerCertCheckException.Reason.EXIST_CHECK_FAILED, - "Uploaded certificate does not exists"); - } + return signerInformationEntity.orElseThrow( + () -> new SignerCertCheckException(SignerCertCheckException.Reason.EXIST_CHECK_FAILED, + "Uploaded certificate does not exists")); } private boolean certificateSignedByCa(X509CertificateHolder certificate, TrustedPartyEntity caCertificateEntity) { diff --git a/src/main/java/eu/europa/ec/dgc/gateway/service/TrustListService.java b/src/main/java/eu/europa/ec/dgc/gateway/service/TrustListService.java index 3639b86e..b91f708d 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/service/TrustListService.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/service/TrustListService.java @@ -24,7 +24,9 @@ import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; import eu.europa.ec.dgc.gateway.model.TrustList; import eu.europa.ec.dgc.gateway.model.TrustListType; +import eu.europa.ec.dgc.gateway.utils.ListUtils; import eu.europa.ec.dgc.utils.CertificateUtils; +import java.time.ZonedDateTime; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -78,7 +80,7 @@ public List getTrustList(TrustListType type) { } /** - * Get a TrustList with TrustList Entries filtered by countriy and type. + * Get a TrustList with TrustList Entries filtered by country and type. * * @param type the type to filter for. * @param countryCode the 2-Digit country code to filter for. @@ -98,6 +100,124 @@ public List getTrustList(TrustListType type, String countryCode) { } } + /** + * Finds a list of TrustList. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param ifModifiedSince since timestamp for filtering TrustList. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of {@link TrustList} ordered by KID + */ + public List getTrustList(ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + + List fullTrustLists; + + if (ifModifiedSince == null) { + fullTrustLists = mergeAndConvert( + trustedPartyService.getCertificates(), + signerInformationService.getSignerInformation()); + } else { + fullTrustLists = mergeAndConvert( + trustedPartyService.getCertificates(ifModifiedSince, null, null), + signerInformationService.getSignerInformation(ifModifiedSince, null, null) + ); + } + if (page != null && size != null) { + return ListUtils.getPage(fullTrustLists, page, size); + } else { + return fullTrustLists; + } + } + + /** + * Finds a list of TrustList filtered by type. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param type the type to filter for. + * @param ifModifiedSince since timestamp for filtering TrustList. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of {@link TrustList} ordered by KID + */ + public List getTrustList(TrustListType type, + ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + List trustListsByType; + + if (ifModifiedSince == null) { + if (type == TrustListType.DSC) { + trustListsByType = mergeAndConvert(Collections.emptyList(), + signerInformationService.getSignerInformation(SignerInformationEntity.CertificateType.DSC)); + } else { + trustListsByType = mergeAndConvert(trustedPartyService.getCertificates(map(type)), + Collections.emptyList()); + } + } else { + if (type == TrustListType.DSC) { + trustListsByType = mergeAndConvert(Collections.emptyList(), + signerInformationService.getSignerInformation(SignerInformationEntity.CertificateType.DSC, + ifModifiedSince, null, null)); + } else { + trustListsByType = mergeAndConvert(trustedPartyService.getCertificates(map(type), + ifModifiedSince, null, null), + Collections.emptyList()); + } + } + if (page != null && size != null) { + return ListUtils.getPage(trustListsByType, page, size); + } else { + return trustListsByType; + } + } + + /** + * Finds a list of TrustList filtered by country and type. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param type the type to filter for. + * @param countryCode the 2-Digit country code to filter for. + * @param ifModifiedSince since timestamp for filtering TrustList. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of {@link TrustList} ordered by KID + */ + public List getTrustList(TrustListType type, String countryCode, + ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + List trustListsByTypeAndCountry; + + if (ifModifiedSince == null) { + if (type == TrustListType.DSC) { + trustListsByTypeAndCountry = mergeAndConvert(Collections.emptyList(), + signerInformationService.getSignerInformation(countryCode, + SignerInformationEntity.CertificateType.DSC)); + } else { + trustListsByTypeAndCountry = mergeAndConvert( + trustedPartyService.getCertificates(countryCode, map(type)), + Collections.emptyList()); + } + } else { + if (type == TrustListType.DSC) { + trustListsByTypeAndCountry = mergeAndConvert(Collections.emptyList(), + signerInformationService.getSignerInformation(countryCode, + SignerInformationEntity.CertificateType.DSC, + ifModifiedSince, null, null)); + } else { + trustListsByTypeAndCountry = mergeAndConvert( + trustedPartyService.getCertificates(countryCode, map(type), + ifModifiedSince, null, null), + Collections.emptyList()); + } + } + if (page != null && size != null) { + return ListUtils.getPage(trustListsByTypeAndCountry, page, size); + } else { + return trustListsByTypeAndCountry; + } + } + private List mergeAndConvert( List trustedPartyList, List signerInformationList) { @@ -130,7 +250,7 @@ private TrustList convert(SignerInformationEntity signerInformationEntity) { map(signerInformationEntity.getCertificateType()), signerInformationEntity.getThumbprint(), signerInformationEntity.getSignature(), - signerInformationEntity.getRawData() + signerInformationEntity.getDeletedAt() == null ? signerInformationEntity.getRawData() : null ); } diff --git a/src/main/java/eu/europa/ec/dgc/gateway/service/TrustedPartyService.java b/src/main/java/eu/europa/ec/dgc/gateway/service/TrustedPartyService.java index a6fde010..1f2e6066 100644 --- a/src/main/java/eu/europa/ec/dgc/gateway/service/TrustedPartyService.java +++ b/src/main/java/eu/europa/ec/dgc/gateway/service/TrustedPartyService.java @@ -24,6 +24,7 @@ import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository; import eu.europa.ec.dgc.gateway.utils.DgcMdc; +import eu.europa.ec.dgc.gateway.utils.ListUtils; import eu.europa.ec.dgc.signing.SignedCertificateMessageParser; import eu.europa.ec.dgc.signing.SignedMessageParser; import eu.europa.ec.dgc.utils.CertificateUtils; @@ -32,6 +33,7 @@ import java.security.KeyStoreException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; import java.util.Base64; import java.util.List; import java.util.Optional; @@ -95,6 +97,117 @@ public List getCertificates(String country, TrustedPartyEnti .collect(Collectors.toList()); } + /** + * Finds a list of Certificates. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param ifModifiedSince since timestamp for filtering Certificate. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of certificates. + */ + public List getCertificates(ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + + List trustedPartyEntityFullList; + + if (ifModifiedSince == null) { + trustedPartyEntityFullList = trustedPartyRepository.findAll() + .stream() + .filter(this::validateCertificateIntegrity) + .collect(Collectors.toList()); + + } else { + trustedPartyEntityFullList = + trustedPartyRepository.getIsSince(ifModifiedSince) + .stream() + .filter(this::validateCertificateIntegrity) + .collect(Collectors.toList()); + } + if (page != null && size != null) { + return ListUtils.getPage(trustedPartyEntityFullList, page, size); + } else { + return trustedPartyEntityFullList; + } + } + + /** + * Finds a list of Certificates by type. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param type type to filter for. + * @param ifModifiedSince since timestamp for filtering Certificate. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of certificates. + */ + public List getCertificates(TrustedPartyEntity.CertificateType type, + ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + + List trustedPartyEntityByTypeList; + + if (ifModifiedSince == null) { + trustedPartyEntityByTypeList = trustedPartyRepository.getByCertificateType(type) + .stream() + .filter(this::validateCertificateIntegrity) + .collect(Collectors.toList()); + + } else { + trustedPartyEntityByTypeList = + trustedPartyRepository.getByCertificateTypeIsSince( + type, ifModifiedSince) + .stream() + .filter(this::validateCertificateIntegrity) + .collect(Collectors.toList()); + } + if (page != null && size != null) { + return ListUtils.getPage(trustedPartyEntityByTypeList, page, size); + } else { + return trustedPartyEntityByTypeList; + } + } + + /** + * Finds a list of Certificates by country and type. + * Optional the list can be filtered by a timestamp and paginated. + * + * @param country country of certificate. + * @param type type to filter for. + * @param ifModifiedSince since timestamp for filtering Certificate. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return List of certificates. + */ + public List getCertificates(String country, + TrustedPartyEntity.CertificateType type, + ZonedDateTime ifModifiedSince, + Integer page, Integer size) { + + List trustedPartyEntityByTypeAndCountryList; + + if (ifModifiedSince == null) { + trustedPartyEntityByTypeAndCountryList = + trustedPartyRepository.getByCountryAndCertificateType(country, type) + .stream() + .filter(this::validateCertificateIntegrity) + .collect(Collectors.toList()); + + } else { + trustedPartyEntityByTypeAndCountryList = + trustedPartyRepository.getByCountryAndCertificateTypeIsSince(country, + type, ifModifiedSince) + .stream() + .filter(this::validateCertificateIntegrity) + .collect(Collectors.toList()); + } + if (page != null && size != null) { + return ListUtils.getPage(trustedPartyEntityByTypeAndCountryList, page, size); + } else { + return trustedPartyEntityByTypeAndCountryList; + } + } + /** * Method to query the db for a certificate. * @@ -119,6 +232,7 @@ public List getCountryList() { return trustedPartyRepository.getCountryCodeList(); } + private boolean validateCertificateIntegrity(TrustedPartyEntity trustedPartyEntity) { DgcMdc.put(MDC_PROP_CERT_THUMBPRINT, trustedPartyEntity.getThumbprint()); diff --git a/src/main/java/eu/europa/ec/dgc/gateway/utils/ListUtils.java b/src/main/java/eu/europa/ec/dgc/gateway/utils/ListUtils.java new file mode 100644 index 00000000..64e9cf64 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/gateway/utils/ListUtils.java @@ -0,0 +1,55 @@ +/*- + * ---license-start + * EU Digital Green Certificate Gateway Service / dgc-gateway + * --- + * Copyright (C) 2021 - 2022 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 eu.europa.ec.dgc.gateway.utils; + +import java.util.Collections; +import java.util.List; + +public class ListUtils { + + private ListUtils(){ + } + + /** + * Returns a sublist of a list of objects based on page index and size. + * + * @param list list for sublist. + * @param page zero-based page index, must NOT be negative. + * @param size number of items in a page to be returned, must be greater than 0. + * @return sublist of a list of objects. + */ + public static List getPage(List list, int page, int size) { + + if (page < 0) { + throw new IllegalArgumentException("Page index must not be less than zero!"); + } + if (size <= 0) { + throw new IllegalArgumentException("Page size must not be less than one!"); + } + + int fromIndex = (page) * size; + if (list == null || list.size() < fromIndex) { + return Collections.emptyList(); + } + return list.subList(fromIndex, Math.min(fromIndex + size, list.size())); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e5a86f2a..d1b249f3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -45,6 +45,8 @@ dgc: distinguished-name: X-SSL-Client-DN revocation: delete-threshold: 14 + signer-information: + delete-threshold: 14 springdoc: api-docs: enabled: false diff --git a/src/main/resources/db/changelog.xml b/src/main/resources/db/changelog.xml index 824ed78c..3d1b6f1b 100644 --- a/src/main/resources/db/changelog.xml +++ b/src/main/resources/db/changelog.xml @@ -16,4 +16,5 @@ + diff --git a/src/main/resources/db/changelog/alter-signer-information-for-deletion.xml b/src/main/resources/db/changelog/alter-signer-information-for-deletion.xml new file mode 100644 index 00000000..70ae0d52 --- /dev/null +++ b/src/main/resources/db/changelog/alter-signer-information-for-deletion.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateMigrationControllerTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateMigrationControllerTest.java index 29009cd4..3d81967e 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateMigrationControllerTest.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/CertificateMigrationControllerTest.java @@ -621,7 +621,7 @@ void testUpdateRevocationWrongCountry() throws Exception { private void createSignerInfo(final String cmsBase64, final X509Certificate certDscEu, final String signature) { signerInformationRepository.save(new SignerInformationEntity( - null, ZonedDateTime.now(), countryCode, certificateUtils.getCertThumbprint(certDscEu), + null, ZonedDateTime.now(), null, countryCode, certificateUtils.getCertThumbprint(certDscEu), cmsBase64, signature, SignerInformationEntity.CertificateType.DSC )); } diff --git a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java index 7c30dd60..e2e9af44 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/restapi/controller/TrustListIntegrationTest.java @@ -20,17 +20,11 @@ package eu.europa.ec.dgc.gateway.restapi.controller; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import eu.europa.ec.dgc.gateway.config.DgcConfigProperties; -import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; -import eu.europa.ec.dgc.gateway.entity.TrustedIssuerEntity; import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository; import eu.europa.ec.dgc.gateway.repository.TrustedIssuerRepository; @@ -39,6 +33,7 @@ import eu.europa.ec.dgc.gateway.restapi.dto.TrustListDto; import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils; import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore; +import eu.europa.ec.dgc.gateway.testdata.SignerInformationTestHelper; import eu.europa.ec.dgc.gateway.testdata.TrustedIssuerTestHelper; import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper; import eu.europa.ec.dgc.utils.CertificateUtils; @@ -46,10 +41,12 @@ import java.security.KeyPairGenerator; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Base64; import java.util.List; import java.util.Optional; +import static org.hamcrest.Matchers.hasSize; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,6 +58,10 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc @@ -78,6 +79,9 @@ class TrustListIntegrationTest { @Autowired TrustedPartyTestHelper trustedPartyTestHelper; + @Autowired + SignerInformationTestHelper signerInformationTestHelper; + @Autowired TrustedIssuerTestHelper trustedIssuerTestHelper; @@ -95,8 +99,14 @@ class TrustListIntegrationTest { private static final String countryCode = "EU"; private static final String authCertSubject = "C=" + countryCode; + private static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since"; + private static final ZoneId gmt = ZoneId.of("GMT"); + private static final ZonedDateTime now = ZonedDateTime.now(gmt); + private static final ZonedDateTime nowMinusOneMinute = ZonedDateTime.now(gmt).minusMinutes(1); + private static final ZonedDateTime nowMinusOneHour = ZonedDateTime.now(gmt).minusHours(1); - X509Certificate certUploadDe, certUploadEu, certCscaDe, certCscaEu, certAuthDe, certAuthEu, certDscDe, certDscEu; + X509Certificate certUploadDe, certUploadEu, certCscaDe, certCscaEu, certAuthDe, certAuthEu, certDscDe, certDscEu, + certUploadDe2, certUploadEu2, certCscaDe2, certCscaEu2, certAuthDe2, certAuthEu2, certDscDe2, certDscEu2, certDscEuDeleted; @BeforeEach void testData() throws Exception { @@ -115,33 +125,197 @@ void testData() throws Exception { certDscDe = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "Test"); certDscEu = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EU", "Test"); - signerInformationRepository.save(new SignerInformationEntity( - null, - ZonedDateTime.now(), - "DE", - certificateUtils.getCertThumbprint(certDscDe), - Base64.getEncoder().encodeToString(certDscDe.getEncoded()), - "sig1", - SignerInformationEntity.CertificateType.DSC - )); - - signerInformationRepository.save(new SignerInformationEntity( - null, - ZonedDateTime.now(), - "EU", - certificateUtils.getCertThumbprint(certDscEu), - Base64.getEncoder().encodeToString(certDscEu.getEncoded()), - "sig2", - SignerInformationEntity.CertificateType.DSC - )); + signerInformationTestHelper.createSignerInformationInDB("DE", "sig1", certDscDe, now); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig2", certDscEu, now); trustedIssuerRepository.saveAll(List.of( - trustedIssuerTestHelper.createTrustedIssuer("EU"), - trustedIssuerTestHelper.createTrustedIssuer("DE"), - trustedIssuerTestHelper.createTrustedIssuer("AT") + trustedIssuerTestHelper.createTrustedIssuer("EU"), + trustedIssuerTestHelper.createTrustedIssuer("DE"), + trustedIssuerTestHelper.createTrustedIssuer("AT") )); } + @Test + void testTrustListDownloadNoFilterIsSince() throws Exception { + prepareTestCertsCreatedAtNowMinusOneHour(); + String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode); + + mockMvc.perform(get("/trustList") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneHour) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1")) + .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2")) + .andExpect(c -> assertTrustListItem(c, certDscDe2, "DE", CertificateTypeDto.DSC, "sig3")) + .andExpect(c -> assertTrustListItem(c, certDscEu2, "EU", CertificateTypeDto.DSC, "sig4")) + .andExpect(c -> assertTrustListItem(c, certDscEuDeleted, "EU", CertificateTypeDto.DSC, null, true)) + .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certCscaDe2, "DE", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certCscaEu2, "EU", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certUploadDe2, "DE", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certUploadEu2, "EU", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certAuthDe2, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthEu2, "EU", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListLength(c, 17)); + + mockMvc.perform(get("/trustList") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneMinute) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 8)); + } + @Test + void testTrustListDownloadNoFilterPageable() throws Exception { + prepareTestCertsCreatedAtNowMinusOneHour(); + String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode); + + mockMvc.perform(get("/trustList?page=0&pagesize=100") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListItem(c, certDscDe, "DE", CertificateTypeDto.DSC, "sig1")) + .andExpect(c -> assertTrustListItem(c, certDscEu, "EU", CertificateTypeDto.DSC, "sig2")) + .andExpect(c -> assertTrustListItem(c, certDscDe2, "DE", CertificateTypeDto.DSC, "sig3")) + .andExpect(c -> assertTrustListItem(c, certDscEu2, "EU", CertificateTypeDto.DSC, "sig4")) + .andExpect(c -> assertTrustListItem(c, certDscEuDeleted, "EU", CertificateTypeDto.DSC, null, true)) + .andExpect(c -> assertTrustListItem(c, certCscaDe, "DE", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certCscaEu, "EU", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certUploadDe, "DE", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certUploadEu, "EU", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certCscaDe2, "DE", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certCscaEu2, "EU", CertificateTypeDto.CSCA, null)) + .andExpect(c -> assertTrustListItem(c, certUploadDe2, "DE", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certUploadEu2, "EU", CertificateTypeDto.UPLOAD, null)) + .andExpect(c -> assertTrustListItem(c, certAuthDe2, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthEu2, "EU", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListLength(c, 17)); + } + + @Test + void testTrustListDownloadNoFilterIsSincePageable() throws Exception { + prepareTestCertsCreatedAtNowMinusOneHour(); + String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode); + + mockMvc.perform(get("/trustList?page=-1&pagesize=10") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneHour) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 17)); + + mockMvc.perform(get("/trustList?page=0&pagesize=10") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneHour) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 10)); + + mockMvc.perform(get("/trustList?page=0&pagesize=100") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneMinute) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 8)); + + mockMvc.perform(get("/trustList?page=1&pagesize=5") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneMinute) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 3)); + + mockMvc.perform(get("/trustList?page=2&pagesize=10") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneMinute) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 0)); + } + + @Test + void testTrustListDownloadNoFilterByTypeAndCountryIsSincePageable() throws Exception { + prepareTestCertsCreatedAtNowMinusOneHour(); + String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode); + + mockMvc.perform(get("/trustList/AUTHENTICATION") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneHour) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthEu, "EU", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthDe2, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthEu2, "EU", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListLength(c, 4)); + + mockMvc.perform(get("/trustList/AUTHENTICATION/DE") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListItem(c, certAuthDe, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListItem(c, certAuthDe2, "DE", CertificateTypeDto.AUTHENTICATION, null)) + .andExpect(c -> assertTrustListLength(c, 2)); + + mockMvc.perform(get("/trustList/DSC?page=0&pagesize=10") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneMinute) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 2)); + + mockMvc.perform(get("/trustList/DSC/DE?page=0&pagesize=10") + .accept(MediaType.APPLICATION_JSON_VALUE) + .header(IF_MODIFIED_SINCE_HEADER, nowMinusOneMinute) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getThumbprint(), authCertHash) + .header(dgcConfigProperties.getCertAuth().getHeaderFields().getDistinguishedName(), authCertSubject) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(c -> assertTrustListLength(c, 1)); + } + @Test void testTrustListDownloadNoFilter() throws Exception { String authCertHash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.AUTHENTICATION, countryCode); @@ -516,7 +690,31 @@ void testBadRequests(String url) throws Exception { .andExpect(status().isBadRequest()); } - private void assertTrustListItem(MvcResult result, X509Certificate certificate, String country, CertificateTypeDto certificateTypeDto, String signature) throws CertificateEncodingException, UnsupportedEncodingException, JsonProcessingException { + private void prepareTestCertsCreatedAtNowMinusOneHour() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec"); + certDscDe2 = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "DE", "DETest"); + certDscEu2 = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "EU", "EUTest"); + certDscEuDeleted = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "EU", "EUTestDeleted"); + signerInformationTestHelper.createSignerInformationInDB("DE", "sig3", certDscDe2, nowMinusOneHour); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig4",certDscEu2, nowMinusOneHour); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig5_deleted",certDscEuDeleted, now.minusHours(2), nowMinusOneHour); + + certUploadDe2 = trustedPartyTestHelper.getTestCert("test1", TrustedPartyEntity.CertificateType.UPLOAD, "DE", nowMinusOneHour); + certCscaDe2 = trustedPartyTestHelper.getTestCert("test2", TrustedPartyEntity.CertificateType.CSCA, "DE", nowMinusOneHour); + certAuthDe2 = trustedPartyTestHelper.getTestCert("test3", TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE", nowMinusOneHour); + certUploadEu2 = trustedPartyTestHelper.getTestCert("test4", TrustedPartyEntity.CertificateType.UPLOAD, "EU", nowMinusOneHour); + certCscaEu2 = trustedPartyTestHelper.getTestCert("test5", TrustedPartyEntity.CertificateType.CSCA, "EU", nowMinusOneHour); + certAuthEu2 = trustedPartyTestHelper.getTestCert("test6", TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU", nowMinusOneHour); + } + + private void assertTrustListItem(MvcResult result, X509Certificate certificate, String country, CertificateTypeDto certificateTypeDto, String signature) throws UnsupportedEncodingException, CertificateEncodingException, JsonProcessingException { + assertTrustListItem(result, certificate, country, certificateTypeDto, signature, false); + } + + private void assertTrustListItem(MvcResult result, X509Certificate certificate, String country, CertificateTypeDto certificateTypeDto, String signature, boolean deleted) throws CertificateEncodingException, UnsupportedEncodingException, JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper() .registerModule(new JavaTimeModule()); List trustList = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { @@ -535,8 +733,11 @@ private void assertTrustListItem(MvcResult result, X509Certificate certificate, Assertions.assertEquals(country, trustListItem.getCountry()); Assertions.assertEquals(certificateTypeDto, trustListItem.getCertificateType()); Assertions.assertEquals(certificateUtils.getCertThumbprint(certificate), trustListItem.getThumbprint()); - Assertions.assertEquals(Base64.getEncoder().encodeToString(certificate.getEncoded()), trustListItem.getRawData()); - + if (deleted) { + Assertions.assertNull(trustListItem.getRawData()); + } else { + Assertions.assertEquals(Base64.getEncoder().encodeToString(certificate.getEncoded()), trustListItem.getRawData()); + } if (signature != null) { Assertions.assertEquals(signature, trustListItem.getSignature()); } diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpServiceTest.java new file mode 100644 index 00000000..592aebd2 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationCleanUpServiceTest.java @@ -0,0 +1,85 @@ +package eu.europa.ec.dgc.gateway.service; + +import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; +import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository; +import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils; +import eu.europa.ec.dgc.utils.CertificateUtils; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.security.KeyPairGenerator; +import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; +import java.util.Base64; +import java.util.List; + +@SpringBootTest(properties = "dgc.signer-information.delete-threshold=14") +@Slf4j +class SignerInformationCleanUpServiceTest { + + @Autowired + CertificateUtils certificateUtils; + + @Autowired + SignerInformationRepository signerInformationRepository; + + @Autowired + SignerInformationCleanUpService underTest; + + + @BeforeEach + public void setup() { + signerInformationRepository.deleteAll(); + } + + + @Test + void testCleanup() throws Exception { + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec"); + X509Certificate x509Certificate1 = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "DETest1"); + X509Certificate x509Certificate2 = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "DETest2"); + X509Certificate x509Certificate3 = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "DETest3"); + + SignerInformationEntity deleted3DaysAgo = createSignerInformationInDB("DE", null, + certificateUtils.getCertThumbprint(x509Certificate2), Base64.getEncoder().encodeToString(x509Certificate1.getEncoded()), + ZonedDateTime.now().minusDays(30), ZonedDateTime.now().minusDays(3)); + + SignerInformationEntity deleted3WeeksAgo = createSignerInformationInDB("DE", null, + certificateUtils.getCertThumbprint(x509Certificate3), Base64.getEncoder().encodeToString(x509Certificate1.getEncoded()), + ZonedDateTime.now().minusDays(40), ZonedDateTime.now().minusDays(21)); + + + SignerInformationEntity notDeleted = createSignerInformationInDB("DE", "sig3", + certificateUtils.getCertThumbprint(x509Certificate1), Base64.getEncoder().encodeToString(x509Certificate1.getEncoded()), + ZonedDateTime.now().minusDays(40), null); + + underTest.cleanup(); + + Assertions.assertEquals(2, signerInformationRepository.count()); + List remaining = signerInformationRepository.findAll(); + Assertions.assertTrue(remaining.stream().anyMatch(it -> it.getId().equals(notDeleted.getId()))); + Assertions.assertTrue(remaining.stream().anyMatch(it -> it.getId().equals(deleted3DaysAgo.getId()))); + Assertions.assertFalse(remaining.stream().anyMatch(it -> it.getId().equals(deleted3WeeksAgo.getId()))); + } + + + + private SignerInformationEntity createSignerInformationInDB(String countryCode, String signature, + String thumbprint, String encoded, ZonedDateTime createdAt, ZonedDateTime deletedAt) throws Exception { + return signerInformationRepository.save(new SignerInformationEntity( + null, + createdAt, + deletedAt, + countryCode, + thumbprint, + encoded, + signature, + SignerInformationEntity.CertificateType.DSC + )); + } +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java index db0be2be..4378a286 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/service/SignerInformationServiceTest.java @@ -20,22 +20,26 @@ package eu.europa.ec.dgc.gateway.service; -import eu.europa.ec.dgc.gateway.config.DgcConfigProperties; import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository; import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils; import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore; +import eu.europa.ec.dgc.gateway.testdata.SignerInformationTestHelper; import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper; import eu.europa.ec.dgc.utils.CertificateUtils; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; import java.util.Base64; +import java.util.List; import java.util.Optional; import org.bouncycastle.cert.X509CertificateHolder; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -43,9 +47,6 @@ @SpringBootTest class SignerInformationServiceTest { - @Autowired - DgcConfigProperties dgcConfigProperties; - @Autowired CertificateUtils certificateUtils; @@ -58,12 +59,147 @@ class SignerInformationServiceTest { @Autowired SignerInformationRepository signerInformationRepository; + @Autowired + SignerInformationTestHelper signerInformationTestHelper; + @Autowired SignerInformationService signerInformationService; private static final String countryCode = "EU"; private static final String dummySignature = "randomStringAsSignatureWhichIsNotValidatedInServiceLevel"; + private static final ZonedDateTime now = ZonedDateTime.now(); + private static final ZonedDateTime nowMinusOneMinute = ZonedDateTime.now().minusMinutes(1); + private static final ZonedDateTime nowMinusOneHour = ZonedDateTime.now().minusHours(1); + + @BeforeEach + void setUp() { + cleanupTestSignerInformation(); + } + + @AfterEach + void tearDown() { + cleanupTestSignerInformation(); + } + + @Test + void testSuccessfulGetSignerInformationIsSincePageable() throws Exception { + long signerInformationEntitiesInDb = signerInformationRepository.count(); + prepareTestSignerInformation(); + + Assertions.assertEquals(signerInformationEntitiesInDb + 7, signerInformationRepository.count()); + + List signerInformationEntities = + signerInformationService.getSignerInformation(null, null, null); + Assertions.assertEquals(7, signerInformationEntities.size()); + Assertions.assertTrue(signerInformationEntities.stream().anyMatch(it -> it.getDeletedAt() != null && it.getSignature() == null)); + + List signerInformationEntities2 = signerInformationService.getSignerInformation( + nowMinusOneMinute, null, null); + Assertions.assertEquals(3, signerInformationEntities2.size()); + + List signerInformationEntities3 = signerInformationService.getSignerInformation( + null, 0, 10); + Assertions.assertEquals(7, signerInformationEntities3.size()); + + List signerInformationEntities4 = signerInformationService.getSignerInformation( + null, 10, 10); + Assertions.assertEquals(0, signerInformationEntities4.size()); + + List signerInformationEntities5 = signerInformationService.getSignerInformation( + nowMinusOneMinute, 0, 10); + Assertions.assertEquals(3, signerInformationEntities5.size()); + + List signerInformationEntities6 = signerInformationService.getSignerInformation( + nowMinusOneMinute, 1, 2); + Assertions.assertEquals(1, signerInformationEntities6.size()); + + } + + @Test + void testFailedGetSignerInformationIsSincePageable() throws Exception { + long signerInformationEntitiesInDb = signerInformationRepository.count(); + prepareTestSignerInformation(); + Assertions.assertEquals(signerInformationEntitiesInDb + 7, signerInformationRepository.count()); + + Assertions.assertThrows(IllegalArgumentException.class, () -> + signerInformationService.getSignerInformation(null, -1, 2)); + Assertions.assertEquals(signerInformationEntitiesInDb + 7, signerInformationRepository.count()); + + Assertions.assertThrows(IllegalArgumentException.class, () -> + signerInformationService.getSignerInformation(null, 0, 0)); + + Assertions.assertThrows(IllegalArgumentException.class, () -> + signerInformationService.getSignerInformation(null, -1, 0)); + + Assertions.assertEquals(signerInformationEntitiesInDb + 7, signerInformationRepository.count()); + + } + + @Test + void testSuccessfulGetSignerInformationByTypeAndCountryIsSincePageable() throws Exception { + long signerInformationEntitiesInDb = signerInformationRepository.count(); + prepareTestSignerInformation(); + + Assertions.assertEquals(signerInformationEntitiesInDb + 7, signerInformationRepository.count()); + + List signerInformationEntities = + signerInformationService.getSignerInformation(SignerInformationEntity.CertificateType.DSC, + null, null, null); + Assertions.assertEquals(7, signerInformationEntities.size()); + + List signerInformationEntities2 = signerInformationService.getSignerInformation( + SignerInformationEntity.CertificateType.DSC, + nowMinusOneMinute, null, null); + Assertions.assertEquals(3, signerInformationEntities2.size()); + + List signerInformationEntities3 = signerInformationService.getSignerInformation( + "DE", SignerInformationEntity.CertificateType.DSC, + null, 0, 10); + Assertions.assertEquals(2, signerInformationEntities3.size()); + + List signerInformationEntities4 = signerInformationService.getSignerInformation( + "DE", SignerInformationEntity.CertificateType.DSC, + nowMinusOneMinute, 0, 10); + Assertions.assertEquals(1, signerInformationEntities4.size()); + + List signerInformationEntities5 = signerInformationService.getSignerInformation( + "D", SignerInformationEntity.CertificateType.DSC, + nowMinusOneHour, 0, 10); + Assertions.assertEquals(0, signerInformationEntities5.size()); + } + + private void cleanupTestSignerInformation() { + signerInformationRepository.deleteAll(); + } + + private void prepareTestSignerInformation() throws Exception { + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec"); + signerInformationTestHelper.createSignerInformationInDB("DE", "sig1", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "DE", "DETest"), nowMinusOneHour); + signerInformationTestHelper.createSignerInformationInDB("DE", "sig2", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "DE", "DETest2"), now); + signerInformationTestHelper.createSignerInformationInDB("AT", "sig3", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "AT", "ATTest"), nowMinusOneHour); + signerInformationTestHelper.createSignerInformationInDB("AT", "sig4", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "AT", "ATTest2"), now); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig5", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "EU", "EUTest"), nowMinusOneHour); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig6", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "EU", "EUTest2"), now); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig7_deleted", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "EU", "EUTest3"), now.minusHours(2), nowMinusOneHour); + } + + @Test void testSuccessfulAddingNewSignerInformationAndDelete() throws Exception { long signerInformationEntitiesInDb = signerInformationRepository.count(); @@ -100,11 +236,15 @@ void testSuccessfulAddingNewSignerInformationAndDelete() throws Exception { countryCode ); - Optional deletedSignerInformationEntity = - signerInformationRepository.getFirstByThumbprint(certificateUtils.getCertThumbprint(payloadCertificate)); + List entities = + signerInformationRepository.getByCertificateType(SignerInformationEntity.CertificateType.DSC); - Assertions.assertTrue(deletedSignerInformationEntity.isEmpty()); - Assertions.assertEquals(signerInformationEntitiesInDb, signerInformationRepository.count()); + Assertions.assertFalse(entities.isEmpty()); + SignerInformationEntity deletedSignerInformationEntity = entities.get(0); + Assertions.assertEquals(createdSignerInformationEntity.get().getThumbprint(), deletedSignerInformationEntity.getThumbprint()); + Assertions.assertNull(deletedSignerInformationEntity.getSignature()); + Assertions.assertEquals(createdSignerInformationEntity.get().getRawData(), deletedSignerInformationEntity.getRawData()); + Assertions.assertEquals(signerInformationEntitiesInDb + 1, signerInformationRepository.count()); } @Test diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java index 1c09021c..efef5f1c 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustListServiceTest.java @@ -20,13 +20,13 @@ package eu.europa.ec.dgc.gateway.service; -import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; import eu.europa.ec.dgc.gateway.model.TrustList; import eu.europa.ec.dgc.gateway.model.TrustListType; import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository; import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository; import eu.europa.ec.dgc.gateway.testdata.CertificateTestUtils; +import eu.europa.ec.dgc.gateway.testdata.SignerInformationTestHelper; import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper; import eu.europa.ec.dgc.utils.CertificateUtils; import java.security.KeyPairGenerator; @@ -54,6 +54,9 @@ class TrustListServiceTest { @Autowired TrustedPartyTestHelper trustedPartyTestHelper; + @Autowired + SignerInformationTestHelper signerInformationTestHelper; + @Autowired TrustListService trustListService; @@ -62,6 +65,10 @@ class TrustListServiceTest { X509Certificate certUploadDe, certUploadEu, certCscaDe, certCscaEu, certAuthDe, certAuthEu, certDscDe, certDscEu; + private static final ZonedDateTime now = ZonedDateTime.now(); + private static final ZonedDateTime nowMinusOneMinute = ZonedDateTime.now().minusMinutes(1); + private static final ZonedDateTime nowMinusOneHour = ZonedDateTime.now().minusHours(1); + @BeforeEach void testData() throws Exception { trustedPartyRepository.deleteAll(); @@ -78,25 +85,31 @@ void testData() throws Exception { certDscDe = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "DE", "Test"); certDscEu = CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), "EU", "Test"); - signerInformationRepository.save(new SignerInformationEntity( - null, - ZonedDateTime.now(), - "DE", - certificateUtils.getCertThumbprint(certDscDe), - Base64.getEncoder().encodeToString(certDscDe.getEncoded()), - "sig1", - SignerInformationEntity.CertificateType.DSC - )); - - signerInformationRepository.save(new SignerInformationEntity( - null, - ZonedDateTime.now(), - "EU", - certificateUtils.getCertThumbprint(certDscEu), - Base64.getEncoder().encodeToString(certDscEu.getEncoded()), - "sig2", - SignerInformationEntity.CertificateType.DSC - )); + signerInformationTestHelper.createSignerInformationInDB("DE", "sig1", certDscDe, now); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig2", certDscEu, now); + } + + @Test + void testSuccessfulGetTrustedListIsSincePageable() throws Exception { + prepareTestCertsCreatedAtNowMinusOneHour(); + + List trustLists = trustListService.getTrustList(null, null, null); + Assertions.assertEquals(16, trustLists.size()); + + List trustLists2 = trustListService.getTrustList(nowMinusOneMinute, + null, null); + Assertions.assertEquals(8, trustLists2.size()); + + List trustLists3 = trustListService.getTrustList(TrustListType.DSC, + nowMinusOneMinute, null, null); + Assertions.assertEquals(2, trustLists3.size()); + + List trustLists4 = trustListService.getTrustList(TrustListType.DSC, null, 0, 10); + Assertions.assertEquals(4, trustLists4.size()); + + List trustLists5 = trustListService.getTrustList(TrustListType.UPLOAD, "DE", + nowMinusOneMinute, 0, 10); + Assertions.assertEquals(1, trustLists5.size()); } @Test @@ -169,6 +182,24 @@ void testTrustListFilterByTypeAndCountry() throws Exception { assertTrustListItem(trustList, certAuthEu, "EU", TrustListType.AUTHENTICATION, null); } + private void prepareTestCertsCreatedAtNowMinusOneHour() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ec"); + signerInformationTestHelper.createSignerInformationInDB("DE", "sig3", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "DE", "DETest"), nowMinusOneHour); + signerInformationTestHelper.createSignerInformationInDB("EU", "sig4", + CertificateTestUtils.generateCertificate(keyPairGenerator.generateKeyPair(), + "EU", "EUTest"), nowMinusOneHour); + + trustedPartyTestHelper.getTestCert("test1", TrustedPartyEntity.CertificateType.UPLOAD, "DE", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test2", TrustedPartyEntity.CertificateType.CSCA, "DE", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test3", TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test4", TrustedPartyEntity.CertificateType.UPLOAD, "EU", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test5", TrustedPartyEntity.CertificateType.CSCA, "EU", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test6", TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU", nowMinusOneHour); + } + + private void assertTrustListItem(List trustList, X509Certificate certificate, String country, TrustListType trustListType, String signature) throws CertificateEncodingException { Optional trustListOptional = trustList .stream() diff --git a/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java index aa607f5a..c6a6522a 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/service/TrustedPartyServiceTest.java @@ -22,9 +22,10 @@ import eu.europa.ec.dgc.gateway.entity.TrustedPartyEntity; import eu.europa.ec.dgc.gateway.repository.TrustedPartyRepository; -import eu.europa.ec.dgc.gateway.testdata.DgcTestKeyStore; import eu.europa.ec.dgc.gateway.testdata.TrustedPartyTestHelper; import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder; +import java.time.ZonedDateTime; +import java.util.List; import java.util.Optional; import org.bouncycastle.cert.X509CertificateHolder; import org.junit.jupiter.api.AfterEach; @@ -45,17 +46,111 @@ class TrustedPartyServiceTest { @Autowired TrustedPartyTestHelper trustedPartyTestHelper; - @Autowired - DgcTestKeyStore dgcTestKeyStore; - private static final String countryCode = "EU"; + private static final ZonedDateTime now = ZonedDateTime.now(); + private static final ZonedDateTime nowMinusOneMinute = ZonedDateTime.now().minusMinutes(1); + private static final ZonedDateTime nowMinusOneHour = ZonedDateTime.now().minusHours(1); + private static final int TEST_CERTIFICATE_LIST_SIZE = 12; + @AfterEach void cleanUp() { // We have to delete all certs after each test because some tests are manipulating certs in DB. trustedPartyRepository.deleteAll(); } + @Test + void testSuccessfulGetTrustedPartyListIsSincePageable() throws Exception { + cleanUp(); + long trustedPartyEntitiesInDb = trustedPartyRepository.count(); + prepareTestTrustedParty(); + Assertions.assertEquals(trustedPartyEntitiesInDb + TEST_CERTIFICATE_LIST_SIZE, trustedPartyRepository.count()); + + List trustedPartyEntities = + trustedPartyService.getCertificates(null, null, null); + Assertions.assertEquals(TEST_CERTIFICATE_LIST_SIZE, trustedPartyEntities.size()); + + List trustedPartyEntities3 = + trustedPartyService.getCertificates( nowMinusOneMinute, null, null); + Assertions.assertEquals(TEST_CERTIFICATE_LIST_SIZE /2, trustedPartyEntities3.size()); + + List trustedPartyEntities4 = + trustedPartyService.getCertificates(null, 0, 10); + Assertions.assertEquals(10, trustedPartyEntities4.size()); + + List trustedPartyEntities5 = + trustedPartyService.getCertificates(null, 0, 100); + Assertions.assertEquals(TEST_CERTIFICATE_LIST_SIZE, trustedPartyEntities5.size()); + + List trustedPartyEntities6 = + trustedPartyService.getCertificates(null, 1, 10); + Assertions.assertEquals(2, trustedPartyEntities6.size()); + + List trustedPartyEntities7 = + trustedPartyService.getCertificates(null, 2, 10); + Assertions.assertEquals(0, trustedPartyEntities7.size()); + + List trustedPartyEntities8 = + trustedPartyService.getCertificates( nowMinusOneMinute, 0, 10); + Assertions.assertEquals(TEST_CERTIFICATE_LIST_SIZE /2, trustedPartyEntities8.size()); + + List trustedPartyEntities9 = + trustedPartyService.getCertificates( nowMinusOneMinute, 1, 10); + Assertions.assertEquals(0, trustedPartyEntities9.size()); + } + + @Test + void testSuccessfulGetTrustedPartyListByTypeAndCountryIsSincePageable() throws Exception { + cleanUp(); + long trustedPartyEntitiesInDb = trustedPartyRepository.count(); + prepareTestTrustedParty(); + Assertions.assertEquals(trustedPartyEntitiesInDb + TEST_CERTIFICATE_LIST_SIZE, + trustedPartyRepository.count()); + + List trustedPartyEntities = + trustedPartyService.getCertificates(TrustedPartyEntity.CertificateType.UPLOAD, + null, null, null); + Assertions.assertEquals(4, trustedPartyEntities.size()); + + List trustedPartyEntities3 = + trustedPartyService.getCertificates(TrustedPartyEntity.CertificateType.CSCA, + nowMinusOneMinute, null, null); + Assertions.assertEquals(2, trustedPartyEntities3.size()); + + List trustedPartyEntities4 = + trustedPartyService.getCertificates(countryCode, TrustedPartyEntity.CertificateType.UPLOAD, + null, 0, 10); + Assertions.assertEquals(2, trustedPartyEntities4.size()); + + List trustedPartyEntities5 = + trustedPartyService.getCertificates(countryCode, TrustedPartyEntity.CertificateType.UPLOAD, + null, 1, 10); + Assertions.assertEquals(0, trustedPartyEntities5.size()); + + List trustedPartyEntities6 = + trustedPartyService.getCertificates(countryCode, TrustedPartyEntity.CertificateType.CSCA, + nowMinusOneMinute, 0, 10); + Assertions.assertEquals(1, trustedPartyEntities6.size()); + + List trustedPartyEntities7 = + trustedPartyService.getCertificates(countryCode, TrustedPartyEntity.CertificateType.CSCA, + nowMinusOneMinute, 1, 10); + Assertions.assertEquals(0, trustedPartyEntities7.size()); + } + + @Test + void testFailedGetTrustedPartyListIsSincePageable() { + Assertions.assertThrows(IllegalArgumentException.class, () -> + trustedPartyService.getCertificates(countryCode, TrustedPartyEntity.CertificateType.CSCA, + null,-1,2)); + + Assertions.assertThrows(IllegalArgumentException.class, () -> + trustedPartyService.getCertificates(null, 0, 0)); + + Assertions.assertThrows(IllegalArgumentException.class, () -> + trustedPartyService.getCertificates(null, -1, 0)); + } + @Test void trustedPartyServiceShouldReturnCertificate() throws Exception { String hash = trustedPartyTestHelper.getHash(TrustedPartyEntity.CertificateType.UPLOAD, countryCode); @@ -159,4 +254,20 @@ void trustedPartyServiceShouldNotReturnCertificateIfSignatureIsFromUnknownTrustA certOptional = trustedPartyService.getCertificate(trustedPartyEntity.getThumbprint(), countryCode, TrustedPartyEntity.CertificateType.CSCA); Assertions.assertTrue(certOptional.isEmpty()); } + + private void prepareTestTrustedParty() throws Exception { + trustedPartyTestHelper.getTestCert("test1", TrustedPartyEntity.CertificateType.UPLOAD, "DE", now); + trustedPartyTestHelper.getTestCert("test2", TrustedPartyEntity.CertificateType.CSCA, "DE", now); + trustedPartyTestHelper.getTestCert("test3", TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE", now); + trustedPartyTestHelper.getTestCert("test4", TrustedPartyEntity.CertificateType.UPLOAD, "DE", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test5", TrustedPartyEntity.CertificateType.CSCA, "DE", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test6", TrustedPartyEntity.CertificateType.AUTHENTICATION, "DE", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test7", TrustedPartyEntity.CertificateType.UPLOAD, "EU", now); + trustedPartyTestHelper.getTestCert("test8", TrustedPartyEntity.CertificateType.CSCA, "EU", now); + trustedPartyTestHelper.getTestCert("test9", TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU", now); + trustedPartyTestHelper.getTestCert("test10", TrustedPartyEntity.CertificateType.UPLOAD, "EU", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test11", TrustedPartyEntity.CertificateType.CSCA, "EU", nowMinusOneHour); + trustedPartyTestHelper.getTestCert("test12", TrustedPartyEntity.CertificateType.AUTHENTICATION, "EU", nowMinusOneHour); + } + } diff --git a/src/test/java/eu/europa/ec/dgc/gateway/testdata/SignerInformationTestHelper.java b/src/test/java/eu/europa/ec/dgc/gateway/testdata/SignerInformationTestHelper.java new file mode 100644 index 00000000..eaf63074 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/gateway/testdata/SignerInformationTestHelper.java @@ -0,0 +1,58 @@ +/*- + * ---license-start + * EU Digital Green Certificate Gateway Service / dgc-gateway + * --- + * Copyright (C) 2021 - 2022 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 eu.europa.ec.dgc.gateway.testdata; + +import eu.europa.ec.dgc.gateway.entity.SignerInformationEntity; +import eu.europa.ec.dgc.gateway.repository.SignerInformationRepository; +import eu.europa.ec.dgc.utils.CertificateUtils; +import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; +import java.util.Base64; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class SignerInformationTestHelper { + + private final SignerInformationRepository signerInformationRepository; + + private final CertificateUtils certificateUtils; + + public void createSignerInformationInDB(String countryCode, String signature, + X509Certificate certificate, ZonedDateTime createdAt) throws Exception { + createSignerInformationInDB(countryCode, signature, certificate, createdAt, null); + } + public void createSignerInformationInDB(String countryCode, String signature, + X509Certificate certificate, ZonedDateTime createdAt, + ZonedDateTime deletedAt) throws Exception { + signerInformationRepository.save(new SignerInformationEntity( + null, + createdAt, + deletedAt, + countryCode, + certificateUtils.getCertThumbprint(certificate), + Base64.getEncoder().encodeToString(certificate.getEncoded()), + deletedAt == null ? signature : null, + SignerInformationEntity.CertificateType.DSC + )); + } +} diff --git a/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java b/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java index aacbded0..257ba19c 100644 --- a/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java +++ b/src/test/java/eu/europa/ec/dgc/gateway/testdata/TrustedPartyTestHelper.java @@ -25,12 +25,11 @@ import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder; import eu.europa.ec.dgc.signing.SignedStringMessageBuilder; import eu.europa.ec.dgc.utils.CertificateUtils; - -import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; @@ -67,6 +66,11 @@ public class TrustedPartyTestHelper { private final DgcTestKeyStore testKeyStore; + public X509Certificate getTestCert(String testCertId, TrustedPartyEntity.CertificateType type, + String countryCode,ZonedDateTime createdAt) throws Exception { + return createAndInsertCert(testCertId, type, countryCode,createdAt); + } + public String getHash(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception { prepareTestCert(type, countryCode); return hashMap.get(type).get(countryCode); @@ -109,40 +113,55 @@ private void prepareTestCert(TrustedPartyEntity.CertificateType type, String cou if (trustedPartyRepository.getFirstByThumbprintAndCertificateType( hashMap.get(type).get(countryCode), type ).isEmpty()) { - insertTestCert(type, countryCode); + insertCert(type, countryCode, null, certificateMap.get(type).get(countryCode)); } } private void createAndInsertCert(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception { KeyPair keyPair = KeyPairGenerator.getInstance("ec").generateKeyPair(); X509Certificate authCertificate = - CertificateTestUtils.generateCertificate(keyPair, countryCode, "DGC Test " + type.name() + " Cert"); - String certHash = certificateUtils.getCertThumbprint(authCertificate); + CertificateTestUtils.generateCertificate(keyPair, countryCode, + "DGC Test " + type.name() + " Cert"); certificateMap.get(type).put(countryCode, authCertificate); - hashMap.get(type).put(countryCode, certHash); + hashMap.get(type).put(countryCode, certificateUtils.getCertThumbprint(authCertificate)); privateKeyMap.get(type).put(countryCode, keyPair.getPrivate()); - insertTestCert(type, countryCode); + insertCert(type, countryCode, null, authCertificate); } - private void insertTestCert(TrustedPartyEntity.CertificateType type, String countryCode) throws Exception { - String certRawData = Base64.getEncoder().encodeToString( - certificateMap.get(type).get(countryCode).getEncoded()); + private X509Certificate createAndInsertCert(String testCertId, TrustedPartyEntity.CertificateType type, + String countryCode, ZonedDateTime createdAt) throws Exception { + KeyPair keyPair = KeyPairGenerator.getInstance("ec").generateKeyPair(); + X509Certificate authCertificate = + CertificateTestUtils.generateCertificate(keyPair, countryCode, + "DGC Test " + type.name() + " Cert-" + testCertId); + + return insertCert(type, countryCode, createdAt, authCertificate); + } + + private X509Certificate insertCert(TrustedPartyEntity.CertificateType type, String countryCode, + ZonedDateTime createdAt, X509Certificate authCertificate) throws Exception { + + String certRawData = Base64.getEncoder().encodeToString(authCertificate.getEncoded()); + String certHash = certificateUtils.getCertThumbprint(authCertificate); String signature = new SignedCertificateMessageBuilder() - .withPayload(new X509CertificateHolder(certificateMap.get(type).get(countryCode).getEncoded())) - .withSigningCertificate(new X509CertificateHolder(testKeyStore.getTrustAnchor().getEncoded()), testKeyStore.getTrustAnchorPrivateKey()) + .withPayload(new X509CertificateHolder(authCertificate.getEncoded())) + .withSigningCertificate(new X509CertificateHolder(testKeyStore.getTrustAnchor().getEncoded()), + testKeyStore.getTrustAnchorPrivateKey()) .buildAsString(true); TrustedPartyEntity trustedPartyEntity = new TrustedPartyEntity(); + if(createdAt != null) trustedPartyEntity.setCreatedAt(createdAt); trustedPartyEntity.setCertificateType(type); trustedPartyEntity.setCountry(countryCode); trustedPartyEntity.setSignature(signature); trustedPartyEntity.setRawData(certRawData); - trustedPartyEntity.setThumbprint(hashMap.get(type).get(countryCode)); + trustedPartyEntity.setThumbprint(certHash); trustedPartyRepository.save(trustedPartyEntity); + return authCertificate; } public String signString(final String hashdata) throws Exception {